在实际Web开发中,我们的dao层、service层是不会处理异常的,因为真正与客户端交流的是controller层,如果我们在dao或service层就把异常处理了,那么我们的客户端是收不到真正的回馈的,可能登录时用户名不存在,或者密码输错了,也没有任何提示或者跳转。那么我们controller层处理异常时,往往需要很多重复繁琐的代码,而且处理异常的逻辑代码要混在控制器层中,高度耦合,不利于我们对代码的重复利用和解耦。
在SpringMVC中,我们可以利用框架提供的@ControllerAdvice 和 @RestControllerAdvice来自定义处理异常的类。这些类可以方便我们封装自己处理异常的方法和逻辑,并且使他们在项目全域生效。这就很好的解决了我们上面的问题。
当你在一个类的上方添加了@ControllerAdvice 或 @RestControllerAdvice注解时,就是告诉Spring这是一个统一处理异常的类。在类中我们需要添加处理异常的方法,并在方法上方添加@ExceptionHandler注解。另外在定义处理异常方法时,还要注意几点:
1.访问权限:应该使用public,只有使用了public才能保证项目全局都能使用
2.返回值类型:参考处理请求的方法,这里要看实际业务需求我们要返回前端什么样的类型的返回值,一般来说我们会自定义一个返回值类型JsonResult,以便于我们向前端返回自己设定的格式和数据。
3.方法名称:自定义
4.参数列表:至少有1个被处理的异常类型参数,可以是任何继承了Throwable的类,可按需添加特定类型的参数,例如HttpServletRequest
、HttpServletResponse
等,但不可以像处理请求的方法那么自由,不能传封装的对象等等。
5.在同一个项目中,可以有多个统一处理异常的类,每个类中都可以有多个处理异常的方法,只要这些方法处理的异常不完全相同(各方法处理的异常允许存在继承关系)即可。
示例代码:先定一个自定义异常类,作为我们自己service层抛出异常使用
@Getter
public class ServiceException extends RuntimeException {
private Integer state;
public ServiceException(Integer state, String message) {
super(message);
this.state = state;
}
}
自定义一个返回值类型类JsonResult
@Data
public class JsonResult<T> implements Serializable {
/**
* 业务状态码
*/
private Integer state;
/**
* 消息
*/
private String message;
/**
* 数据
*/
private T data;
public static JsonResult ok() {
return ok(null);
}
public static <T> JsonResult<T> ok(T data) {
JsonResult jsonResult = new JsonResult();
jsonResult.setState(ServiceCode.OK);
jsonResult.setData(data);
return jsonResult;
}
public static JsonResult<Void> fail(ServiceException e) {
return fail(e.getState(), e.getMessage());
}
public static JsonResult<Void> fail(Integer state, String message) {
JsonResult jsonResult = new JsonResult();
jsonResult.setState(state);
jsonResult.setMessage(message);
return jsonResult;
}
}
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
public GlobalExceptionHandler() {
log.debug("创建统一处理异常的类:GlobalExceptionHandler");
}
@ExceptionHandler
public JsonResult<Void> handleServiceException(ServiceException e) {
log.debug("处理ServiceException:{}", e.getMessage());
return JsonResult.fail(e);
}
@ExceptionHandler
public JsonResult<Void> handleBindException(BindException e) {
log.debug("处理BindException:{}", e.getMessage());
StringBuilder stringBuilder = new StringBuilder();
List<FieldError> fieldErrors = e.getFieldErrors();
for (FieldError fieldError : fieldErrors) {
String message = fieldError.getDefaultMessage();
stringBuilder.append(message);
}
return JsonResult.fail(ServiceCode.ERR_BAD_REQUEST, stringBuilder.toString());
}
@ExceptionHandler
public JsonResult<Void> handleThrowable(Throwable e) {
log.debug("处理Throwable");
e.printStackTrace();
String message = "程序运行过程中出现意外错误,请联系系统管理员!";
return JsonResult.fail(ServiceCode.ERR_INTERNAL_SERVER_ERROR, message);
}
}
最后这个处理Throwable的异常算是兜底异常,当控制台出现这样的异常提示时,我们就要注意,程序出现了我们未曾处理过的异常。就要注意了。