在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里面的定义的错误码,在前端会有想要的字段与其对应,完成非常友好的提示工作。

       对于抛出一个异常,不要觉得就是不好的,这样可以让用户去检查自己的操作是否正确,并给出相应的提示工作。

       到此关于全局异常统一处理就完成了,对于这个功能点,能够降低维护成本,使代码看起来更加的简洁,感兴趣的可以了解一下其具体的实现。