相信大家在平时写代码的时候都使用过 try catch 来处理异常,特别是在前端调用的后端接口中,如果我们没做异常处理,后端直接返回错误信息给前端,前端直接把程序员才看懂的错误信息展示给了用户,想必会造成很不好的用户体验。
为了防止发生这种情况,我们就需要在后端接口中 try catch 处理好异常,将更友好的错误信息返回给用户,比如:服务器内部异常、校验异常等等。而我们一个个的在接口中加上 try catch 有些麻烦,此时我们就可以使用全局异常处理机制 。
@ControllerAdvice 注解实现全局异常处理
在 Spring 3.2 中,新增了@ControllerAdvice 、@RestControllerAdvice 注解。这两个注解的功能呢其实是一样的。区别就像 @Controller 和 @RestController 之间的区别。
@ControllerAdvice 作用:
- 全局异常处理
- 全局数据绑定
- 全局数据预处理
@ControllerAdvice 用法:
@ExceptionHandler 注解标注的方法:用于捕获 Controller 中抛出的不同类型的异常,从而达到异常全局处理的目的;@InitBinder 注解标注的方法:用于请求中注册自定义参数的解析,从而达到自定义请求参数格式的目的;@ModelAttribute 注解标注的方法:表示此方法会在执行目标 Controller 方法之前执行 。
全局异常处理具体用法:
今天呐,我们主要来探讨的是 @ControllerAdvice 结合 @ExceptionHandler 注解用于全局异常的处理。
至于 @ControllerAdvice 的其他作用,本文不做探讨。有兴趣的小伙伴可以自己学习下。
代码实现:
创建一个 GlobalExceptionHandler 类,添加上 @RestControllerAdvice 注解
@RestControllerAdvice
public class GlobalExceptionHandler
{
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(BaseException.class)
public AjaxResult baseException(BaseException e)
{
return AjaxResult.error(e.getCode(),e.getDefaultMessage());
}
@ExceptionHandler(CustomException.class)
public AjaxResult businessException(CustomException e)
{
if (StringUtils.isNull(e.getCode()))
{
return AjaxResult.error(e.getMessage());
}
return AjaxResult.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public AjaxResult handleException(Exception e)
{
log.error(e.getMessage(), e);
return AjaxResult.error(HttpStatus.ERROR,"系统内部错误");
}
@ExceptionHandler(BindException.class)
public AjaxResult validatedBindException(BindException e)
{
log.error(e.getMessage(), e);
String message = e.getAllErrors().get(0).getDefaultMessage();
return AjaxResult.error(message);
}
@ExceptionHandler(PreAuthorizeException.class)
public AjaxResult preAuthorizeException(PreAuthorizeException e)
{
return AjaxResult.error("没有权限,请联系管理员授权");
}
}
在该类中,可以定义多个方法,不同的方法处理不同的异常,例如你还可以定义专门处理空指针的方法、专门处理数组越界的方法等等。Controller 中的不同的异常就会进入这个类中的对应异常的方法中。
来看一下我们自定义的BaseException 异常:
public class BaseException extends RuntimeException
{
private static final long serialVersionUID = 1L;
private String module;
private String code = HttpStatus.ERROR;
private Object[] args;
private String defaultMessage;
public BaseException(String module, String code, Object[] args, String defaultMessage)
{
this.module = module;
this.code = code;
this.args = args;
this.defaultMessage = defaultMessage;
}
public BaseException(String module, String code, Object[] args)
{
this(module, code, args, null);
}
public BaseException(String code, String defaultMessage)
{
this(null, code, null, defaultMessage);
}
public BaseException(String code, Object[] args)
{
this(null, code, args, null);
}
public BaseException(String defaultMessage)
{
this(null, HttpStatus.ERROR, null, defaultMessage);
}
public String getModule()
{
return module;
}
public String getCode()
{
return code;
}
public Object[] getArgs()
{
return args;
}
public String getDefaultMessage()
{
return defaultMessage;
}
}
再贴出 AjaxResult.java 的代码(来自开源项目若依管理系统):
public class AjaxResult extends HashMap<String, Object>
{
private static final long serialVersionUID = 1L;
public static final String CODE_TAG = "code";
public static final String MSG_TAG = "msg";
public static final String DATA_TAG = "data";
public AjaxResult()
{
}
public AjaxResult(String code, String msg)
{
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
}
public AjaxResult(String code, String msg, Object data)
{
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
if (StringUtils.isNotNull(data))
{
super.put(DATA_TAG, data);
}
}
@Override
public AjaxResult put(String key, Object value)
{
super.put(key, value);
return this;
}
public static AjaxResult success()
{
return AjaxResult.success("操作成功");
}
public static AjaxResult success(Object data)
{
return AjaxResult.success("操作成功", data);
}
public static AjaxResult success(String msg)
{
return AjaxResult.success(msg, null);
}
public static AjaxResult success(String msg, Object data)
{
return new AjaxResult(HttpStatus.SUCCESS, msg, data);
}
public static AjaxResult error()
{
return AjaxResult.error("操作失败");
}
public static AjaxResult error(String msg)
{
return AjaxResult.error(HttpStatus.ERROR, msg);
}
public static AjaxResult error(String msg, Object data)
{
return new AjaxResult(HttpStatus.ERROR, msg, data);
}
public static AjaxResult error(String code, String msg)
{
return new AjaxResult(code, msg, null);
}
public static AjaxResult error(String code, String msg, Object data)
{
return new AjaxResult(code, msg, data);
}
}
总结
除了使用 @ControllerAdvice 之外,还可以通过实现 HandlerExceptionResolver 接口类实现全局异常处理机制。这里就不在叙述了,感兴趣的小伙伴自己去查。
基于 @ControllerAdvice 实现,我们平时写代码的时候,就会在 Service 层抛出异常到 Controller 层,而不是直接 try catch 处理。如果自己 try catch 处理了,那么就不会再进入到 GlobalExceptionHandler 类中了。
当然除了返回规范的错误信息给用户,你也可以在 GlobalExceptionHandler 方法中做其他的工作,比如异常统计什么的。
还有一点想要写一下:
在 service 方法里面如果对异常进行了捕获并处理的话,该事务是不会进行回滚的,@Transactional会失效
或者在 catch 语句中最后增加 throw new RuntimeException() 语句,以便让 aop 捕获异常再去回滚。
又或者在 catch 语句中增加下面代码来手动回滚:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|