前言:项目之前一直用的springboot默认的异常处理BasicErrorController,因业务需要还是需要自定义一个异常处理类,springboot 下有两种方式:继承ErrorController 或 @ControllerAdvice/@RestControllerAdvice, 我自己选用ControllerAdvice注解来自定义全局异常处理,结果发现不生效。

  • 网上搜索后可能的问题

    • bean没被spring扫描到
      用中文搜索ControllerAdvice不生效多半是这个搜索结果
    • 项目里有多个ControllerAdvice注解的bean
      因为项目不是第一手的,所以我还是去查了一下,可惜不是这个原因
    • 自定义异常处理类 extends ResponseEntityExceptionHandler 没有覆写方法,导致被父类处理

我的原因:

最后网上搜了半天,都是以上几种回答,想了下估计这个是自己的问题了,再仔细看了看我的代码:

@ExceptionHandler({ BaseException.class, IllegalArgumentException.class })
	public Object runtimeExceptionHandler(HttpServletRequest request, BaseException e) {
//略
}

ExceptionHandler注解的方法我的方法参数是HttpServletRequest request, BaseException e, 看了一下网上的例子是用的Exception,猜测是BaseException不在可选的参数列表里,这个只有改成Exception了,要用的时候手动instance of 判断强转。

后续:

凡是靠猜都是不行的,写代码更是不行,为了记录下这次情况,专门debug看一下是咋调用的:

  • 初始化异常处理映射
    ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache
    private void initExceptionHandlerAdviceCache() {
        if (this.getApplicationContext() != null) {
            List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(this.getApplicationContext());
            Iterator var2 = adviceBeans.iterator();

            while(var2.hasNext()) {
                ControllerAdviceBean adviceBean = (ControllerAdviceBean)var2.next();
                Class<?> beanType = adviceBean.getBeanType();
                if (beanType == null) {
                    throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
                }

                ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
                if (resolver.hasExceptionMappings()) {
                    this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
                }

                if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
                    this.responseBodyAdvice.add(adviceBean);
                }
            }

            if (this.logger.isDebugEnabled()) {
                int handlerSize = this.exceptionHandlerAdviceCache.size();
                int adviceSize = this.responseBodyAdvice.size();
                if (handlerSize == 0 && adviceSize == 0) {
                    this.logger.debug("ControllerAdvice beans: none");
                } else {
//springboot 启动时能看到这里输出了我写的自定义异常处理类
                    this.logger.debug("ControllerAdvice beans: " + handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");
                }
            }

        }
    }
  • 异常处理类的调用
    ExceptionHandlerExceptionResolver#doResolveHandlerMethodException
    @Nullable
    protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
        ServletInvocableHandlerMethod exceptionHandlerMethod = this.getExceptionHandlerMethod(handlerMethod, exception);
        if (exceptionHandlerMethod == null) {
            return null;
        } else {
            if (this.argumentResolvers != null) {
                exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
            }

            if (this.returnValueHandlers != null) {
                exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
            }

            ServletWebRequest webRequest = new ServletWebRequest(request, response);
            ModelAndViewContainer mavContainer = new ModelAndViewContainer();
            ArrayList exceptions = new ArrayList();

            try {
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
                }

                Throwable cause;
                for(Object exToExpose = exception; exToExpose != null; exToExpose = cause != exToExpose ? cause : null) {
                    exceptions.add(exToExpose);
                    cause = ((Throwable)exToExpose).getCause();
                }

                Object[] arguments = new Object[exceptions.size() + 1];
                exceptions.toArray(arguments);
                arguments[arguments.length - 1] = handlerMethod;
//这里就是调用我们自己的异常处理方法了
                exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);
            } catch (Throwable var13) {
                if (!exceptions.contains(var13) && this.logger.isWarnEnabled()) {
                    this.logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, var13);
                }

                return null;
            }

            if (mavContainer.isRequestHandled()) {
                return new ModelAndView();
            } else {
                ModelMap model = mavContainer.getModel();
                HttpStatus status = mavContainer.getStatus();
                ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
                mav.setViewName(mavContainer.getViewName());
                if (!mavContainer.isViewReference()) {
                    mav.setView((View)mavContainer.getView());
                }

                if (model instanceof RedirectAttributes) {
                    Map<String, ?> flashAttributes = ((RedirectAttributes)model).getFlashAttributes();
                    RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
                }

                return mav;
            }
        }
    }
  • exceptionHandlerMethod.invokeAndHandle内部实现
    ServletInvocableHandlerMethod#invokeAndHandle
    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
//这行代码执行后的 returnValue 就是我们自定义异常处理方法执行后的返回值
        Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);
        this.setResponseStatus(webRequest);
        if (returnValue == null) {
            if (this.isRequestNotModified(webRequest) || this.getResponseStatus() != null || mavContainer.isRequestHandled()) {
                this.disableContentCachingIfNecessary(webRequest);
                mavContainer.setRequestHandled(true);
                return;
            }
        } else if (StringUtils.hasText(this.getResponseStatusReason())) {
            mavContainer.setRequestHandled(true);
            return;
        }

        mavContainer.setRequestHandled(false);
        Assert.state(this.returnValueHandlers != null, "No return value handlers");

        try {
            this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);
        } catch (Exception var6) {
            if (logger.isTraceEnabled()) {
                logger.trace(this.formatErrorForReturnValue(returnValue), var6);
            }

            throw var6;
        }
    }
  • this.invokeForRequest 内部实现
    @Nullable
    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
//这个地方是关键,就是这个方法里的判断导致之前的异常处理方法失效了
        Object[] args = this.getMethodArgumentValues(request, mavContainer, providedArgs);
        if (logger.isTraceEnabled()) {
            logger.trace("Arguments: " + Arrays.toString(args));
        }

        return this.doInvoke(args);
    }
  • 关键地方,就是这里的实现导致失效
    protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
        MethodParameter[] parameters = this.getMethodParameters();
        if (ObjectUtils.isEmpty(parameters)) {
            return EMPTY_ARGS;
        } else {
            Object[] args = new Object[parameters.length];

            for(int i = 0; i < parameters.length; ++i) {
                MethodParameter parameter = parameters[i];
                parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
                args[i] = findProvidedArgument(parameter, providedArgs);
//因为取不到对应的参数 args[i] == null
                if (args[i] == null) {
                    if (!this.resolvers.supportsParameter(parameter)) {
                        throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
                    }

                    try {
                        args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
                    } catch (Exception var10) {
                        if (logger.isDebugEnabled()) {
                            String exMsg = var10.getMessage();
                            if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                                logger.debug(formatArgumentError(parameter, exMsg));
                            }
                        }

                        throw var10;
                    }
                }
            }

            return args;
        }
    }
    @Nullable
    protected static Object findProvidedArgument(MethodParameter parameter, @Nullable Object... providedArgs) {
        if (!ObjectUtils.isEmpty(providedArgs)) {
            Object[] var2 = providedArgs;
            int var3 = providedArgs.length;

            for(int var4 = 0; var4 < var3; ++var4) {
                Object providedArg = var2[var4];
//这里进行参数类型校验
                if (parameter.getParameterType().isInstance(providedArg)) {
                    return providedArg;
                }
            }
        }

        return null;
    }

总结

之所以当时没有生效, 是因为我的注解是这样写的:@ExceptionHandler({ BaseException.class, IllegalArgumentException.class })
而这样当抛IllegalArgumentException异常时也会调用,但是我方法里写的参数是BaseException,当IllegalArgumentException异常发生并调用时只有IllegalArgumentException参数传入,findProvidedArgument方法找不到对应类型参数,所以这里抛异常了,最终没有调用我们的异常处理方法,解决方法就是改为Exception,或者ExceptionHandler注解只能有方法参数的父类异常。

Q.E.D.


我并不是什么都知道,我只是知道我所知道的。