@ControllerAdvice到底是怎么一回事?

1,693 阅读7分钟

1. 前言

系统开发过程中,异常处理与业务同样重要,完善的异常处理才能让业务系统更加健壮。下面会介绍如何实现全局异常处理以及全局异常处理实现的原理。

2.全局异常实现

2.1 @ControllerAdvice声明

@ControllerAdvice
public class GlobalExceptionHandler {
}

2.2 @ExceptionHandler声明

@ExceptionHandler(value = MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public BaseResponse<?> handleMethodArgumentException(MethodArgumentNotValidException exception) {
    List<FieldErrorfieldErrors = exception.getBindingResult().getFieldErrors();
    Set<StringerrorMessage = new HashSet<>();
    for (FieldError fieldError : fieldErrors) {
        errorMessage.add(fieldError.getDefaultMessage());
    }
    BaseResponse<?> baseResponse = new BaseResponse<>();
    baseResponse.setCode(10001);
    baseResponse.setMsg(errorMessage.toString());
    return baseResponse;
}

3.全局异常原理

3.1 声明异常解析器

@Bean
public HandlerExceptionResolver handlerExceptionResolver(
    @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
    List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
    configureHandlerExceptionResolvers(exceptionResolvers);
    if (exceptionResolvers.isEmpty()) {
        addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
    }
    extendHandlerExceptionResolvers(exceptionResolvers);
    HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
    composite.setOrder(0);
    composite.setExceptionResolvers(exceptionResolvers);
    return composite;
}

3.1.1 添加默认异常解析器

protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers,
   ContentNegotiationManager mvcContentNegotiationManager) {

    ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
    exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager);
    exceptionHandlerResolver.setMessageConverters(getMessageConverters());
    exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
    exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
    if (jackson2Present) {
        exceptionHandlerResolver.setResponseBodyAdvice(
            Collections.singletonList(new JsonViewResponseBodyAdvice()));
    }
    if (this.applicationContext != null) {
        exceptionHandlerResolver.setApplicationContext(this.applicationContext);
    }
    exceptionHandlerResolver.afterPropertiesSet();
    exceptionResolvers.add(exceptionHandlerResolver);

    ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
    responseStatusResolver.setMessageSource(this.applicationContext);
    exceptionResolvers.add(responseStatusResolver);

    exceptionResolvers.add(new DefaultHandlerExceptionResolver());
}

3.1.2 ExceptionHandlerExceptionResolver异常解析器初始化

public void afterPropertiesSet() {
    // Do this first, it may add ResponseBodyAdvice beans
    initExceptionHandlerAdviceCache();

    if (this.argumentResolvers == null) {
        List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    if (this.returnValueHandlers == null) {
        List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
        this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    }
}

3.1.3 处理@ControllerAdvice

private void initExceptionHandlerAdviceCache() {
    // 1. 遍历所有被@ControllerAdvice注解标注的Bean,生成ControllerAdviceBean对象
    List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
    for (ControllerAdviceBean adviceBean : adviceBeans) {
        Class<?> beanType = adviceBean.getBeanType();
        // 2. 创建ExceptionHandlerMethodResolver对象,遍历对应类下面的所有方法,解析方法上的@ExceptionHandler注解,将异常类型与目标方法映射关系存入mappedMethods集合中
        ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
        if (resolver.hasExceptionMappings()) {
            // 3. 将ControllerAdviceBean与ExceptionHandlerMethodResolver映射关系存入map中
            this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
        }
        if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
            this.responseBodyAdvice.add(adviceBean);
        }
    }
}

3.2 初始化异常解析器

DispatcherServlet初始化方法中不仅会初始化HandlerMappingsHandlerAdapters,还会初始化HandlerExceptionResolvers

protected void initStrategies(ApplicationContext context) {
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
}

3.3 处理异常

我们都知道DispatcherServletdoDispatch()方法用来处理客户端的请求

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
  HttpServletRequest processedRequest = request;
  HandlerExecutionChain mappedHandler = null;
  boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;
        try {
            // Determine handler for the current request.
            mappedHandler = getHandler(processedRequest);
            // Determine handler adapter for the current request.
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
            // 处理业务方法
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        }
        catch (Exception ex) {
            dispatchException = ex;
        }
        catch (Throwable err) {
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    } catch (Exception ex) {
    }
    catch (Throwable err) {   
    }
    finally {
    }
}

如上逻辑可以得知mv = ha.handle(processedRequest, response, mappedHandler.getHandler());用来处理业务方法,假设业务方法抛出异常,异常会被catch块捕获,程序会进入processDispatchResult()方法

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
   @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
   @Nullable Exception exception) throws Exception {

    boolean errorView = false;

    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        }
        else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }
}

3.3.1 遍历异常处理器解析异常

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
   @Nullable Object handler, Exception ex) throws Exception {
    
    // Check registered HandlerExceptionResolvers...
    ModelAndView exMv = null;
    if (this.handlerExceptionResolvers != null) {
        // 遍历异常解析器解析异常
        for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
            exMv = resolver.resolveException(request, response, handler, ex);
            if (exMv != null) {
                break;
            }
        }
    }
    throw ex;
}

3.3.2 根据异常类型找到目标方法

protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
   @Nullable HandlerMethod handlerMethod, Exception exception) {

    Class<?> handlerType = null;
    for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
        ControllerAdviceBean advice = entry.getKey();
        if (advice.isApplicableToBeanType(handlerType)) {
            ExceptionHandlerMethodResolver resolver = entry.getValue();
            Method method = resolver.resolveMethod(exception);
            if (method != null) {
                return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
            }
        }
    }
    return null;
}

3.1.3章节可以了解到exceptionHandlerAdviceCache结构存放的是ControllerAdviceBeanExceptionHandlerMethodResolver的映射关系,ExceptionHandlerMethodResolver中的mappedMethods结构存放的是异常类型目标方法的映射关系,因此Method method = resolver.resolveMethod(exception);可以通过异常类型得到目标方法,有了目标方法就可以利用反射来执行,也就是@ControllerAdvice中被@ExceptionHandler注解标注匹配异常类型对应的方法