统一异常处理

Scroll Down

异常处理

业务开发的场景中 总避免不了这个场景,如何实现是,首先想到的就是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());
        }
    }
}