1.前言
在构建一个服务时,总会抛出各种异常,这时我们就需要进行统一的异常处理,这样就能保证对外有一致的返回。
而且可以避免通过返回值判断函数调用结果这种繁琐的代码。
2.Controller层方法,进行统一异常处理
提供两种不同的方案,如下:
- 方案1:使用
@ControllerAdvice (或@RestControllerAdvice), @ExceptionHandler
注解实现; - 方案2: 使用AOP技术实现;
3.使用@ControllerAdvice注解的方式
3.1 创建一个全局异常处理类
如下的代码会对我们项目中的所有Controller的抛出异常进行处理,这里我们引入了自定义异常ServiceException
可以声明要捕获的异常,比如@ExceptionHandler(NoHandlerFoundException.class)
可以指定拦截的包,@RestControllerAdvice(basePackages = "com.demo.springboot_helloword.webrest")
/**
* Created on 2019/1/15 19:09.
*
* @author caogu
*/
@RestControllerAdvice
public class ControllerExceptionHandleAdvice {
private final static Logger logger = LoggerFactory.getLogger(ControllerExceptionHandleAdvice.class);
@ExceptionHandler
public BaseResult handler(HttpServletRequest request, HttpServletResponse response, Exception e) {
logger.error("Restful Http请求发生异常, Path=" + request.getServletPath(), e);
if (response.getStatus() == HttpStatus.BAD_REQUEST.value()) {
logger.info("修改返回状态值,oldStatus={} newStatus={}", response.getStatus(), HttpStatus.BAD_REQUEST.value());
response.setStatus(HttpStatus.OK.value());
}
BaseResult baseResult = new BaseResult();
if (e instanceof ServiceException) {
ServiceException exception = (ServiceException) e;
baseResult.setCode(exception.getCode());
baseResult.setMessage(exception.getMessage());
} else {
baseResult.setCode(PlatformErrorCode.SERVICE_INTERNAL_ERROR.getCode());
baseResult.setMessage(PlatformErrorCode.SERVICE_INTERNAL_ERROR.getMessage());
}
return baseResult;
}
}
3.2 创建一个自定义异常
一个业务异常,一般需要错误码和错误信息。有助于我们来定位问题和统一对外接口行为。
/**
* Created on 2019/1/14 11:37.
*
* @author caogu
*/
public class ServiceException extends RuntimeException {
/**
* 错误码
*/
private String code;
/*public ServiceException(String message, String code) {
super(message);
this.code = code;
}*/
public ServiceException(BusinessErrorCode businessErrorCode) {
super(businessErrorCode.getMessage());
this.code = businessErrorCode.getCode();
}
public ServiceException(BusinessErrorCode businessErrorCode, Throwable cause) {
super(businessErrorCode.getMessage(), cause);
this.code = businessErrorCode.getCode();
}
public ServiceException(PlatformErrorCode platformErrorCode) {
super(platformErrorCode.getMessage());
this.code = platformErrorCode.getCode();
}
public ServiceException(PlatformErrorCode businessErrorCode, Throwable cause) {
super(businessErrorCode.getMessage(), cause);
this.code = businessErrorCode.getCode();
}
/*public ServiceException(String message, String code, Throwable cause) {
super(message, cause);
this.code = code;
}*/
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
这时候,我们发现还有一个问题,如果这样写,在代码多起来以后,很难管理这些业务异常和错误码之间的对应关系。所以在优化一下。
把错误码及错误信息,组装成枚举来统一管理。
定义一个业务异常的枚举。
3.3 业务异常枚举
package com.sinosun.starter.enums;
/**
* Created on 2019/1/14 12:10.
*
* @author caogu
*/
public enum PlatformErrorCode {
SERVICE_INTERNAL_ERROR(1, "服务器内部错误")
;
private static final int BASE_CODE_VALUE = 10000;
private static final String BASE_CODE_PREFIX = "P";
private String code;
private String message;
PlatformErrorCode(int code, String message) {
this.code = buildCode(code);
this.message = message;
}
private String buildCode(int code) {
int codeValue = BASE_CODE_VALUE + code;
return BASE_CODE_PREFIX + codeValue;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
}
3.4 结果测试
在代码中抛出一个异常后,统一异常处理就会生效,如下所示:
public StationResult getAllCity(NoneRequest requestBody) {
throw new ServiceException(PlatformErrorCode.SERVICE_INTERNAL_ERROR);
//return new StationResult(new StationList(PreloadData.getTrainAllCity()));
}
4.使用AOP技术
主要思路:通过AOP的@Around拦截所有的Controller方法,在调用处统一处理异常。还可在调用前调用后打印日志等操作。
例如:
package com.sinosun.starter.config;
/**
* Created on 2019/1/16 8:44.
*
* @author caogu
*/
@Component
@Aspect
public class ControllerAspect {
private static final Logger logger = LoggerFactory.getLogger(ControllerAspect.class);
@Pointcut("execution(public * com.sinosun.starter.controller..*.*(..))")
public void allControllerFunction() {
}
@Around("allControllerFunction()")
public Object handleControllerMethod(ProceedingJoinPoint pjp) {
Stopwatch stopwatch = Stopwatch.createStarted();
BaseResult baseResult;
try {
logger.info("执行Controller方法{}开始,参数:{}", pjp.getSignature().getName(), Arrays.toString(pjp.getArgs()));
baseResult = (BaseResult) pjp.proceed(pjp.getArgs());
logger.info("执行Controller方法{}结束,返回值:{}", pjp.getSignature().getName(), baseResult);
logger.info("执行Controller方法{}耗时:{}(ms).", pjp.getSignature().getName(), stopwatch.stop().elapsed(TimeUnit.MILLISECONDS));
} catch (Throwable throwable) {
baseResult = handlerException(pjp, throwable);
}
return baseResult;
}
private BaseResult handlerException(ProceedingJoinPoint pjp, Throwable e) {
BaseResult baseResult = new BaseResult();
logger.error("方法:" + pjp.getSignature().getName() + ", 参数:" + Arrays.toString(pjp.getArgs()) + ",异常:" + e.getMessage() + "}", e);
if (e instanceof ServiceException) {
ServiceException exception = (ServiceException) e;
baseResult.setCode(exception.getCode());
baseResult.setMessage(exception.getMessage());
} else {
baseResult.setCode(PlatformErrorCode.SERVICE_INTERNAL_ERROR.getCode());
baseResult.setMessage(PlatformErrorCode.SERVICE_INTERNAL_ERROR.getMessage());
}
return baseResult;
}
}
结果:
5.结论
- 异常处理推荐使用
@ControllerAdvice (或@RestControllerAdvice), @ExceptionHandler
注解方案。 - 异常处理不推介使用AOP技术,使用 AOP技术作为出入口日志打印,异常处理比较麻烦,对所有的返回值需要手动设置response。
- 这两者应该结合使用,@ControllerAdvice处理异常,使用AOP统一记录出入口日志。