在spring 3.2中,新增了@ControllerAdvice,@RestControllerAdvice 注解,可以用于定义@ExceptionHandler、@InitBinder、@ModelAttribute,并应用到所有@RequestMapping中。参考帮助文档。@RestControllerAdvice 是组件注解,他使得其实现类能够被classpath扫描自动发现,如果应用是通过MVC命令空间或MVC Java编程方式配置,那么该特性默认是自动开启的。
@RestControllerAdvice相当于Controller的切面,对异常统一处理,定制,这样更好返回给前端。
本文介绍使用@RestControllerAdvice和@ExceptionHandler完成全局异常统一处理。
之前在项目中,Controller层里面经常可以看到如下的代码:
@RestController("/ui/test")
public class TestController {
@Autowired
TestService testService;
private static final Logger logger = LoggerFactory.getLogger(TestController.class);
@GetMapping("/query")
public BaseResponse<List<TestEntity>> query(ViewForm viewForm){
BaseResponse<List<TestEntity>> response = new BaseResponse<>();
try {
List<TestEntity> testEntity = testService.queryTestByPage(viewForm);
response.setData(testEntity);
} catch (Exception e) {
response.setCode(ErrorCodeEnum.OEFD_GRADE_QUERY_ERROR.getCode());
response.setMsg(ErrorCodeEnum.OEFD_GRADE_QUERY_ERROR.getMessage());
//记录日志
logger.error("query grade error. ", e);
}
return response;
}
@PostMapping("/save")
public BaseResponse<TestEntity> save(@RequestBody @Valid ViewForm viewForm){
BaseResponse<TestEntity> response = new BaseResponse<>();
try {
TestEntity testEntity = testService.saveRegion(viewForm);
response.setData(testEntity);
}catch(BaseRuntimeException baseRuntimeException){
response.setCode(e1.getCode());
response.setMsg(e1.getMessage());
logger.error("save grade params error. ", baseRuntimeException);
}catch (Exception e) {
response.setCode(ErrorCodeEnum.OEFD_GRADE_SAVE_ERROR.getCode());
response.setMsg(ErrorCodeEnum.OEFD_GRADE_SAVE_ERROR.getMessage());
logger.error("save grade error. ", e);
}
return response;
}
}
可以看到,在Controller层都要进行异常的捕获,然后根据异常信息,给前段返回相应的提示,像save方法捕获了两个异常,其中对于BaseRuntimeException是应用里面自定义的异常,比如有一个必须要填写的字段为空,那么在testService里面校验后,会抛出一个BaseRuntimeException的异常,然后,在Controller层进行捕获,进行相应的处理,而Exception用来捕获未知的异常,在运行时可能发生的异常都将在Exception中被捕获。
对于Controller层来说,每一个方法几乎都要进行相同的操作,这样看就显得代码冗余度较高,基本只有调用testService的方法是有用的,其他的都是冗余的代码,使得代码整体看上去不美观,且不好维护。
下面就使用@RestControllerAdvice和@ExceptionHandler完成对全局异常处理的改造。
一:IErrorCode接口
public interface IErrorCode {
/**
* 获取错误码
* @return
*/
String getCode();
/**
* 获取错误信息
* @return
*/
String getMessage();
}
此接口为统一的错误码接口,业务中可以按模块或按业务自定义枚举错误码,实现该接口,UserErrorCode、CommonErrorCode,内部定义相应的错误码信息。
二:IErrorCode接口的具体实现:
public enum CommonErrorCode implements IErrorCode {
//长度超过限制
LENGTH_EXCEEDS_THE_LIMIT("100010", "errorCode.0x2c130310.description"),
//请求参数错误
REQUEST_PARAM_ERROR("100011", "errorCode.0x2c130311.description"),
//用户id为空
USER_ID_IS_EMPTY("100012", "errorCode.0x2c130312.description"),
//无该用户
USER_IS_NOT_FOUND("100013", "errorCode.0x2c130313.description"),
// 发送消息失败
SEND_MSG_ERROR("100014", "errorCode.0x2c130314.description"),
// SQL执行异常
SQL_EXCEPTION("10001a","errorCode.0x2c13031a.description"),
// 数据库连接异常
DATASOURCE_CONNECTION_ERROR("10001b", "errorCode.0x2c13031b.description"),
ERR_GET_FILE_ADDR_ERROR("10001c", "file address get error"),
ERR_UPLOAD_FILE_ERROR("10001d", "upload file error");
private String code;
private String message;
CommonErrorCode(String code, String message) {
this.code = code;
this.message = message;
}
@Override
public String getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
}
public enum FileErrorCode implements IErrorCode {
//文件上传
File_UPLOAD_ERROR("100100", "errorCode.0x2c130200.description"),
//目录创建错误
DIRECTORY_CREATE_ERROR("100101", "errorCode.0x2c130201.description"),
//模板不存在
TEMPLATE_NOT_EXISTS("100102", "errorCode.0x2c130202.description"),
//模板下载错误
TEMPLATE_DOWNLOAD_ERROR("100103", "errorCode.0x2c130203.description"),
//模板导入非法
TEMPLATE_IMPORT_ILLEGAL("100104", "errorCode.0x2c130204.description"),
TEMPLATE_IMPORT_OVVERSIZE("100105", "errorCode.0x2c130205.description"),
//模板导入错误
TEMPLATE_IMPORT_ERROR("100106", "errorCode.0x2c130206.description"),
TEMPLATE_IMPORT_EMPTY("100107", "errorCode.0x2c130207.description"),
TEMPLATE_FORMAT_ERROR("100108", "errorCode.0x2c130208.description"),
TEMPLATE_EXAMPLE_LINE("100109", "errorCode.0x2c130209.description"),
TEMPLATE_PROCESS_ERROR("100110", "errorCode.0x2c130210.description"),
;
private String code;
private String message;
FileErrorCode(String code, String msg) {
this.code = code;
this.message = msg;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
}
这里说一下,错误码可以按照 服务 + 业务 + 顺序 进行唯一指定一个错误码,可以将系统中常见的错误码进行抽离,然后针对每个服务在进行指定特定的错误码的方式,对于每一个定义错误码业务类都要实现IErrorCode接口,这样便于后面的处理。
三:自定义异常类----BaseRuntimeException
public class BaseRuntimeException extends RuntimeException{
private String code;
private Object[] params;
public BaseRuntimeException(String message){
super(message);
}
public BaseRuntimeException(String message, Throwable cause){
super(message, cause);
}
public BaseRuntimeException(String code,String message){
super(message);
this.code = code;
}
public BaseRuntimeException(Throwable cause, String code,String message){
super(message, cause);
this.code = code;
}
public BaseRuntimeException(String code, String message,Object... params){
super(message);
this.code = code;
this.params = params;
}
public BaseRuntimeException(IErrorCode errorCode){
super(errorCode.getMessage());
this.code = errorCode.getCode();
}
public BaseRuntimeException(IErrorCode errorCode, Object[] args){
super(errorCode.getMessage());
this.code = errorCode.getCode();
if (null == args) {
} else {
this.params = args.clone();
}
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Object[] getParams() {
if (null == params) {
return null;
} else {
return params.clone();
}
}
public void setParams(Object[] params) {
if (null == params) {
this.params = null;
} else {
this.params = params.clone();
}
}
}
四:全局异常处理类---------GlobalExceptionHandler
@RestControllerAdvice
public class GlobalExceptionHandler {
private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler
public BaseResponse handle(BaseRuntimeException exception) {
//errorCode处理
String code = ResponseUtil.formatErrorCode(exception.getCode());
//记录错误日志
logger.error(LogFormatter.toLog(code, exception.getMessage()), exception);
//进行统一格式的错误返回
return BaseResponseBuilder.createResponse(code, exception.getMessage(), exception.getParams());
}
/**
* 其他没有处理的错误
*
* @param e
* @return
*/
@ExceptionHandler
public BaseResponse<Object> handle(Exception e) {
//记录日志
logger.error(LogFormatter.toLog(DefaultErrorCode.ERROR), e);
//DefaultErrorCode.ERROR为未归类错误码
return BaseResponseBuilder.createResponse(DefaultErrorCode.ERROR);
}
}
在GlobalExceptionHandler类中捕获了BaseRuntimeException和Exception异常,如果发生BaseRuntimeException的异常,会被方法一进行捕获,其他的异常都会被方法二给捕获,如果想要捕获其他的具体的异常,可以在这个类中在进行定义就行了。
五: 具体使用
@RestController("/ui/test")
public class TestController {
@Autowired
TestService testService;
@GetMapping("/query")
public BaseResponse<List<TestEntity>> query(ViewForm viewForm){
BaseResponse<List<TestEntity>> response = new BaseResponse<>();
List<TestEntity> testEntity = gradeService.queryTestByPage(viewForm);
response.setData(testEntity);
return response;
}
@PostMapping("/save")
public BaseResponse<TestEntity> save(@RequestBody @Valid ViewForm viewForm){
BaseResponse<TestEntity> response = new BaseResponse<>();
TestEntity testEntity = gradeService.saveRegion(viewForm);
response.setData(testEntity);
return response;
}
}
@Service
public class TestServiceImpl implements TestService{
@Override
public List<TestEntity> queryTestByPage(ViewForm viewForm) {
if(viewForm == null || StringUtils.isEmpty(view.name)){
throw new BaseRuntimeException(CommonErrorCode.REQUEST_PARAM_ERROR)
}
//xxx进行业务处理
}
}
当检测失败时,TestServiceImpl 会直接抛出一个BaseRuntimeException的异常,然后就会被GlobalExceptionHandler类中的方法进行捕获,根据CommonErrorCode.REQUEST_PARAM_ERROR里面的定义的错误码,在前端会有想要的字段与其对应,完成非常友好的提示工作。
对于抛出一个异常,不要觉得就是不好的,这样可以让用户去检查自己的操作是否正确,并给出相应的提示工作。
到此关于全局异常统一处理就完成了,对于这个功能点,能够降低维护成本,使代码看起来更加的简洁,感兴趣的可以了解一下其具体的实现。