异常处理
业务开发的场景中 总避免不了这个场景,如何实现是,首先想到的就是AOP。ok AOP的确可以做到,但是是否有点大材小用呢,Spring3.2版本以后为我们提供了一个@ControllerAdvice注解,使用该注解就可以统一拦截异常。
这个注解配合@ExceptionHandler就可以实现所有@RequestMapping的异常捕获。进行统一处理。单独针对异常进行的处理,更急的职责单一。相较于手写aop方式,节省代码量。而且实现比较简单,本文也会附上AOP实现方式
Http请求异常处理
如下为主要的相关联的注解,一般在统一异常处理场景下较多使用的为@ExceptionHandler注解
- @ExceptionHandler 可以作用于Servlet容器和Portlet容器,和处理程序类中异常绑定。
- @InitBinder 在controller方法执行之前进行参数绑定
- @ModelAttribute 在Model中增加属性。
针对@ExceptionHandler 的value需要特殊注意
如果这个value的值得粒度非常粗的话,会导致异常混乱,因为默认情况下被@ControllerAdvice修饰类中的所有被@ExceptionHandler注解的方法会织入给每一个controller中被@RequestMapping修饰的方法,也就说每一个请求执行如果抛出异常那么就会匹配所所有被@ExceptionHandler注解的方法。粗暴的方式就是写一个方法,然后在这一个方法中进行业务异常的一个[instanceof]然后进行异常的分类处理,比如进行钉钉播报之流的操作。
@ControllerAdvice注解默认识别的是全局的Controller,也可以指定细粒度的扫描。比如指定basePackages,还有如针对特殊@RestController注解的增强。
/**
* @author: by Mood
* @description: 增加统一异常处理切面
* @date: 2019-04-29 16:43:45
*/
@ControllerAdvice(basePackages = {"com.xxx.xxx.controller"})
@Slf4j
public class CenterControllerAdvice {
/**
* @description: 增加统一异常处理钉钉播报
* @param exception
* @param method
* @param request
* @return
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Result handle(Exception exception, HandlerMethod method, HttpServletRequest request) {
StringBuffer stringBuffer=new StringBuffer();
stringBuffer.append(method.getMethod().getDeclaringClass().getSimpleName())
.append("#")
.append(method.getMethod().getName());
String requestUrl="";
RequestMapping annotation= method.getMethod().getDeclaringClass().getAnnotation(RequestMapping.class);
if (null !=annotation){
requestUrl+=annotation.value()[0];
}
stringBuffer.append(requestUrl);
stringBuffer.append(MDC.get("request_id"));
// 钉钉播报
Notify.broadcast(stringBuffer.toString());
if (exception instanceof SystemRemoteException){
return Result.error("-1","业务调用报错");
}else if (exception instanceof HystrixInvokeException){
return Result.error("-1","服务熔断报错");
}
...
else{
return Result.error("-1","系统报错");
}
}
}
使用AOP监控Controller方式,这里说明一下这个AOP中的execution表达式。execution(* com.xxx.xxx.controller..*Controller.*(..))
第一个*标识任意返回值,com.xxx.xxx.controller··
标识扫描的包以及子包 *Controller
标识切入被扫描包下的以Controller结束的类。第三个*
代表任意方法,..
标识任意数量的参数列表。
/**
*不变的AOP。菜鸡互啄。
*/
@Aspect
@Slf4j
@Component
public class ApiAspect {
@Around("execution(* com.xxx.xxx.controller..*Controller.*(..))")
public void around(ProceedingJoinPoint pjp) throws Throwable {
StringBuffer stringBuffer=new StringBuffer();
try{
。。。
pjp.proceed();
}cache(Exception exception){
// 钉钉播报
Notify.broadcast(stringBuffer.toString());
if (exception instanceof SystemRemoteException){
return Result.error("-1","业务调用报错");
}else if (exception instanceof HystrixInvokeException){
return Result.error("-1","服务熔断报错");
}
...
else{
return Result.error("-1","系统报错");
}
}finally{
}
}
}
定时任务异常处理
基于@ControllerAdvice的方式可以监控Http请求,当时业务开发场景中,一定有定时任务这种。这样的话就无法使用@ControllerAdvice了,应为可能执行是压根就没有http请求到访,这是就得使用AOP了。因为有的类可能是非接口实现,就是单纯的java类,这样的话可能导致引入可能无法监控到,或者会导致报错,因为SpringBoot AOP默认是JDK动态代理,需要指定一下强制使用CGLIb代理。
/**
* @author: Mood
* @description: 定时任务切面监控
* @date: 2019-04-28 19:38:44
*/
@Aspect
@Component
@Slf4j
public class JobMonitorAspect {
@Autowired
private CenterExceptionHandler exceptionHandler;
/**
* 监控标注为@Monitor注解的类当这些类执行的时候调用环绕
* <pre>
* @Monitor("XXXXX同步任务")
* @Override
* public void execute(ShardingContext shardingContext) {
* ......do something
* }
* </pre>
* @param joinPoint
* @param monitor
* @throws Throwable
*/
@Around("@annotation(monitor)" )
public void invokeMethod(ProceedingJoinPoint joinPoint, Monitor monitor) throws Throwable {
StopWatch stopwatch =new StopWatch();
stopwatch.start(monitor.value());
StringBuilder sb = new StringBuilder();
String caller = ApiAspect.getCaller(joinPoint);
sb.append(caller).append(SymbolConstant.BAR);
String desc = ApiAspect.getInterfaceDesc(joinPoint);
sb.append(desc).append(SymbolConstant.BAR);
try {
joinPoint.proceed();
} catch (Throwable ex) {
sb.append(ex.getMessage());
log.info(sb.toString());
exceptionHandler.handleExceptionBroadcast(monitor.value(),ex, sb);
} finally {
stopwatch.stop();
sb.append(stopwatch.getLastTaskInfo());
log.info(sb.toString());
}
}
}