异常

介绍

  在Java中,如果某个方法不能以正常的途径完成任务,可以通过另一个路径退出方法。此时,方法并不返回任何值,而是抛出throw一个封装了错误信息的对象

异常分类

  所有异常都继承自Throwable,并分解为ErrorException

  Error分支的类结构主要负责描述运行时系统的内部错误和资源耗尽错误。程序不应该抛出此类错误,出现此错误时除了告诉客户外还应该使程序安全的终止。

  Exception分为两个分支,RuntimeException其它异常

  由程序错误导致的异常属于RuntimeException,程序本身没问题但因为IO等错误导致的异常属于其它异常。

RuntimeException包括:

  • 错误的类型转换
  • 数组访问越界
  • 访问null指针

其它异常包括:

  • 试图在文件尾部后面读取数据
  • 试图打开不存在的文件
  • 试图根据给定字符串查找Class对象,但字符串表示的类并不存在

  Java语言规范将派生于Error和RuntimeException的异常称为非受查异常unchecked,其它异常成为受查异常checked。

  若子类重写了父类的方法,其声明的受查异常不能比父类声明异常更通用。

应用

  可以创建自己的异常类,继承Exception或其子类,然后至少要创建默认构造器和包含错误描述信息的构造器。

1
2
3
4
5
6
7
8
9
public class CustomException extends Exception {

public CustomException() {
}

public CustomException(String message) {
super(message);
}
}

  实际项目开发会设计一些常用的异常类,如登陆时UserNotFoundException、PwdNotMatchException、下载时FileNotFoundException等。可以根据实际情况设计项目所需要的各类异常。

1
2
3
4
5
6
7
8
public class ResourceNotEnoughException extends RuntimeException {
public ResourceNotEnoughException() {
}

public ResourceNotEnoughException(String message) {
super(message);
}
}

抛出异常

  1. 找到合适的异常类
  2. 创建异常类的对象
  3. 抛出对象
1
2
3
4
5
6
7
8
9
public int countValue(String s) throws NullPointerException{
return s.length();
}

public void method(){
...
throw new RuntimeException();//各类运行时异常
}

什么时候抛出异常?

  try-catch,try-with-resource有谈到选择抛出还是处理异常需要根据情况判断,具体哪些情况下抛出异常会好一些呢?

  1. 无法处理异常,打印和输出并不能算是异常处理,统一抛出异常。
  2. 异常机制设计了统一异常处理,按照机制的规定情况抛出异常。
  3. 具体情况具体分析,切忌偷懒滥用。

捕获异常

  通过try/catch语句块来捕获异常,具体可参考try-catch,try-with-resource


简单的设计项目内的异常机制

  业务流程不能正确执行通常有以下原因:

  1. 输入必填项验证未通过,或数据根本就没有非空验证
  2. 业务状态验证未通过
  3. 权限验证未通过
  4. 调用外部服务接口时,返回数据不符合预期

  软件一些常见的异常还有:

  1. 服务调用异常:超时、中断、状态码非200的调用异常
  2. 第三方异常:数据库、缓存、消息队列等
  3. 代码常规异常:JDK定义的那些Exception

  1. 根据以上和实际项目需求,分类设计各种异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ApiException extends RuntimeException {
private Integer code;
private String errMsg;

public ApiException(CodeEnums codeEnums) {
super(codeEnums.getMsg());
this.code = codeEnums.getCode();
this.errMsg = codeEnums.getMsg();
}

public ApiException(int code,String msg) {
super(msg);
this.code = code;
this.errMsg = msg;
}
}

@Getter
public enum CodeEnums {
SUCCESS(0, "操作成功"),
SYSTEM_ERR(1, "系统异常"),
PARA_ERR(2, "[参数异常]:"),
USER_EXIST(10001, "用户已经注册"),
PWD_CHECK_ERR(10002, "重复密码有误"),
USER_NOT_EXIST(10003, "用户不存在"),
USER_IS_LOCK(10004, "用户状态被锁定"),
USER_PWD_FAIL(10005, "登录密码错误"),
USER_TOKEN_FAIL(10006, "鉴权失败,请重新登录"),
USER_TOKEN_EMPTY(10007, "获取鉴权信息失败"),
USER_ROLE_LIMIT(10008, "权限不足");

private Integer code;
private String msg;
CodeEnums(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}

  2. 实现全局异常拦截处理,如Spring中可以通过@ControllerAdvice或继承AbstractHandlerExceptionResolver来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
@ControllerAdvice
public class GlobalExceptionHandler {
private static Logger logger = Logger.getLogger(GlobalExceptionHandler.class);
private static final String DEFAULT_ERROR_VIEW = "/error";

@ExceptionHandler(value = RuntimeException.class)
@ResponseBody
public String handleRuntimeException(RuntimeException ex) {//可以返回JSON数据
logger.error("捕获异常-RuntimeException");
logger.error(ex.getMessage());
ex.printStackTrace();//不要隐藏异常栈信息
JSONObject object = new JSONObject();
object.put("msg", ex.getMessage());
object.put("code", 1);
return object.toString();
}

@ExceptionHandler(value = ApiException.class)
@ResponseBody
public String handleRuntimeException(ApiException ex) {
logger.error("捕获异常-ApiException");
logger.error(ex.getMessage());
ex.printStackTrace();
JSONObject object = new JSONObject();
object.put("msg", ex.getMessage());
object.put("code", 1);
return object.toString();
}

@ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
public ModelAndView handleIllegalParamException(HttpRequestMethodNotSupportedException ex) {//也可以返回某个页面
logger.error("捕获异常-HttpRequestMethodNotSupportedException");
logger.error(ex.getMessage());
ex.printStackTrace();
ModelAndView mav = new ModelAndView();
mav.addObject("errorMsg", "错误的访问系统服务,服务接口不存在");
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
}

@ExceptionHandler(value = MissingServletRequestParameterException.class)
public ModelAndView handleNullPointerException(MissingServletRequestParameterException ex) {
logger.error("捕获异常-MissingServletRequestParameterException");
logger.error(ex.getMessage());
ex.printStackTrace();
ModelAndView mav = new ModelAndView();
mav.addObject("errorMsg", "缺少服务所需要的参数");
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
}

@ExceptionHandler(value = HttpSessionRequiredException.class)
public ModelAndView handleHttpSessionRequiredException(HttpSessionRequiredException ex) {
logger.error("捕获异常-HttpSessionRequiredException");
logger.error(ex.getMessage());
ex.printStackTrace();
ModelAndView mav = new ModelAndView();
mav.addObject("errorMsg", "登陆已过时,请重新登陆");
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
}

@ExceptionHandler(value = JDBCConnectionException.class)
public ModelAndView handleJDBCConnectionException(JDBCConnectionException ex) {
logger.error("捕获异常-JDBCConnectionException");
logger.error(ex.getMessage());
ex.printStackTrace();
ModelAndView mav = new ModelAndView();
mav.addObject("errorMsg", "网络连接失常,无法连接数据库");
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
}

@ExceptionHandler(value = FileNotFoundException.class)
public ModelAndView handleFileNotFoundException(FileNotFoundException ex) {
logger.error("捕获异常-FileNotFoundException");
logger.error(ex.getMessage());
ex.printStackTrace();
ModelAndView mav = new ModelAndView();
mav.addObject("errorMsg", "找不到指定文件,请联系管理员");
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
}
}

  3. 在业务中适当位置抛出异常

1
2
3
4
public void method(){
...
throw new ApiException(CodeEnums.EMAIL_USED);
}

异常机制的使用

  1. 不要滥用异常机制,

  2. 不要过分细化异常

  3. 利用异常层次结构,不要只抛出RuntimeException,应该找到合适的子类或创建自定义异常。

  4. 处理异常时,在出错的地方抛出异常永远比取虚拟值或默认值要可靠

  5. 一些情况下应该多抛出异常,而不是在函数内捕获异常。

  个人在实际开发中感觉没有必要去规范化的check Exception,无需频繁使用try/catch,编写工具类或基层代码时可以严谨一些,并严格按照Java异常体系分层设计。而业务部分则可以考虑设计统一的运行时异常来描述业务问题,并配置全局捕获异常并处理,没必要在正常业务流程中很少发生的异常上面花费过多精力和时间。


参考博客和文章书籍等:

《Java核心技术 卷Ⅰ》

因博客主等未标明不可引用,若部分内容涉及侵权请及时告知,我会尽快修改和删除相关内容