springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理

作者 : admin 本文共5960个字,预计阅读时间需要15分钟 发布时间: 2024-06-16 共1人阅读

文章目录

  • springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理
    • 配置全局异常处理器的三种方式
      • 实现接口HandlerExceptionResolver并配置到WebMvcConfigurer
      • 注解式配置@ExceptionHandler
      • controller里方法上定义@ExceptionHandler
    • 深入源码分析
      • 进入DispatcherServlet
      • 执行handler方法并catch异常

springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理

配置全局异常处理器的三种方式

实现接口HandlerExceptionResolver并配置到WebMvcConfigurer

定义一个类实现这个接口


/**
 * Interface to be implemented by objects that can resolve exceptions thrown during
 * handler mapping or execution, in the typical case to error views. Implementors are
 * typically registered as beans in the application context.
 *
 * 

Error views are analogous to JSP error pages but can be used with any kind of * exception including any checked exception, with potentially fine-grained mappings for * specific handlers. * * @author Juergen Hoeller * @since 22.11.2003 */ public interface HandlerExceptionResolver { /** * Try to resolve the given exception that got thrown during handler execution, * returning a {@link ModelAndView} that represents a specific error page if appropriate. *

The returned {@code ModelAndView} may be {@linkplain ModelAndView#isEmpty() empty} * to indicate that the exception has been resolved successfully but that no view * should be rendered, for instance by setting a status code. * @param request current HTTP request * @param response current HTTP response * @param handler the executed handler, or {@code null} if none chosen at the * time of the exception (for example, if multipart resolution failed) * @param ex the exception that got thrown during handler execution * @return a corresponding {@code ModelAndView} to forward to, * or {@code null} for default processing in the resolution chain */ @Nullable ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex); }

定义一个配置类实现接口 WebMvcConfigurer 并重写方法configureHandlerExceptionResolvers将你定义的这个resolver加入到resolvers这个List即可

注解式配置@ExceptionHandler

要让spring扫描到这个类,一种方式是直接把这个类放到springboot能扫描的目录下(启动类当前目录或者子目录,或者在启动类手动配置的扫描组件目录),另一种方式是放在工具类里,定义到spring.factories里让spring扫描到,注意设置个@Order(0)让这个优先于springmvc注解错误处理器之前注入到容器,否则会无效,因为那个处理器只加载一次,后续不会再扫容器里的实例了。

/**
 * @author humorchen
 * date: 2024/6/11
 * description: 认证全局异常配置
 **/

@Slf4j
@RestControllerAdvice
@Order(0)
public class AuthExceptionResolver {
    public static final int TOKEN_ERROR_CODE = HttpStatus.HTTP_UNAUTHORIZED;
    public static final String ERROR_MSG = "未登录或登录已失效!";


    /**
     * TOKEN相关异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler({SaTokenException.class, JWTVerificationException.class})
    public Result<String> resolveAuthTokenException(Exception e) {
        log.info("handleSaTokenException {}: {}", e.getClass().getName(), e.getMessage());
        return Result.fail(TOKEN_ERROR_CODE, ERROR_MSG);
    }

}

看到上面代码,你会有一个疑问,要是定义了多个,它怎么确定该用哪个呢,源码中使用ExceptionDepthComparator排序,排序规则是按照继承顺序来(继承关系越靠近数值越小,当前类最小为0,顶级父类Throwable为int最大值),排序之后选取继承关系最靠近的那个,并且ExceptionHandlerMethodResolver的exceptionLookupCache中,key为当前抛出的异常,value为解析出来的匹配method.
org.springframework.core.ExceptionDepthComparator
springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图

controller里方法上定义@ExceptionHandler

@RestController
public class ExceptionController {
	
    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception ex) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("An error occurred: " + ex.getMessage());
    }
    @RequestMapping("/test")
    public String test() throws Exception {
        throw new Exception("Test exception!");
    }
}

深入源码分析

进入DispatcherServlet

springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图(1)

执行handler方法并catch异常

springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图(2)
调用processDispatchResult处理分发处理结果
springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图(3)

发现有异常则调用processHandlerException 处理handler异常
springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图(4)

如果有handlerExceptionResovlers就循环调用,直到有一个resolver返回了非空值就跳出循环
springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图(5)
默认resolver,就是把这个异常放到了request.attribute里头去了

springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图(6)

springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图(7)

HandlerExceptionResolverComposite,是handler异常处理器的复合类,把这些resolver放到它内部一个集合
springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图(8)
springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图(9)
点开这个类源码看下,其实就是在我们在webmvcconfigurer里配置这个方法的时候,把配置的所有resolver设置进这个类里的this.resolvers 集合去了,进调试可以看到有两个resolver,一个是我自定义并配置上的ExceptionResolver,另一个是个Http请求异常处理器(seata的HttpHandlerExceptionResolver)
springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图(10)

springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图(11)
分布式事务用来清理ID的,不用管

springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图(12)
第三个也是我注入的那个错误处理器
如果经过这些处理器,有一个返回了非空的结果,这个异常 处理就会被结束

springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图(13)
执行完所有的resolver,有一个返回了ModelAndView的话就返回这个ModelAndView,否则继续抛出异常往上层传递。然后触发请求handler的triggerAfterCompletion,也就是执行你所有跟这个handler绑定的HandlerInterceptor的afterCompletion

springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图(14)

前面看的都是HandlerExceptionResolver的实现,还有那个注解式的实现,我们也一起看下这个ExceptionHandlerExceptionResolver 类的源码,就是用来处理 @ExceptionHandler的

/**
 * An {@link AbstractHandlerMethodExceptionResolver} that resolves exceptions
 * through {@code @ExceptionHandler} methods.
 *
 * 

Support for custom argument and return value types can be added via * {@link #setCustomArgumentResolvers} and {@link #setCustomReturnValueHandlers}. * Or alternatively to re-configure all argument and return value types use * {@link #setArgumentResolvers} and {@link #setReturnValueHandlers(List)}. * * @author Rossen Stoyanchev * @author Juergen Hoeller * @since 3.1 */ public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver implements ApplicationContextAware, InitializingBean { @Nullable private List<HandlerMethodArgumentResolver> customArgumentResolvers; @Nullable private HandlerMethodArgumentResolverComposite argumentResolvers; @Nullable private List<HandlerMethodReturnValueHandler> customReturnValueHandlers; @Nullable private HandlerMethodReturnValueHandlerComposite returnValueHandlers; private List<HttpMessageConverter<?>> messageConverters; private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager(); private final List<Object> responseBodyAdvice = new ArrayList<>(); @Nullable private ApplicationContext applicationContext; private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap<>(64); private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache = new LinkedHashMap<>();

在这个bean执行到afterPropertiesSet这个时间点的时候进行了 @ExceptionHandler实现的扫描initExceptionHandlerAdviceCache(),如果你的 @ExceptionHandler配置bean在这个时间点之后注入进IOC容器的话,那么恭喜你,你的配置不会生效,因此前面我说要让自己的这个配置优先级变高,才能生效。
springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图(15)
ControllerAdviceBean.findAnnotatedBeans(getApplicationContext())
把所有的标注了@ControllerAdvice注解的bean全部找出来并根据order排序返回了

springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图(16)

springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图(17)

看到这你会疑问还有个@RestControllerAdvice,来看源码,@RestControllerAdvice上标注了@ControllerAdvice
springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图(18)
前面已经看了加载过程,现在看执行处理异常的代码,其实就是拿着加载好的处理器,去调用处理,然后参数呢又是可选的,随便你定义,那个其实是反射拿到你方法定义的参数,然后跟现有能给你的一些参数比对类型,然后组成一个参数数组,传入执行对象、参数反射执行方法,然后就拿到了异常处理结果

springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图(19)
组装生成调用参数数组的方法就在这org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues
springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图(20)
也就是把异常对象和当前的handlerMethod给你了,你自己可以写到参数里它会注入给你的
springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图(21)

springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理插图(22)

ExceptionHandler原理另一个作者也写的不错:
从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理

本站无任何商业行为
个人在线分享 » springmvc 全局异常处理器配置的三种方式&深入底层源码分析原理
E-->