zoukankan      html  css  js  c++  java
  • 源码剖析Springboot自定义异常

      博主看到新服务是封装的自定义异常,准备入手剖析一下,自定义的异常是如何进行抓住我们请求的方法的异常,并进行封装返回到。废话不多说,先看看如何才能实现封装异常,先来一个示例:

     1 @ControllerAdvice
     2 public class TstExceptionHandle{
     3 
     4     @ExceptionHandler(Exception.class)
     5     public void myExceptionHandle(HttpServletResponse response){
     6         response.setStatus(403);
     7         System.out.println("做封装处理");
     8     }
     9 
    10 }

      博主只做了简单的配置示例,主要的是进行源码剖析Springboot是如何获取自定义异常并进行返回的。来吧!

      第一步:肯定是在Springboot启动的过程中进行的异常处理初始化,于是就找到了handlerExceptionResolver类,在创建该类的时候,会进行添加我们自定义异常。

     1     public HandlerExceptionResolver handlerExceptionResolver(
     2             @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
     3         List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
     4         //不用管这个方法,这个方法主要进行的是调用实现了WebMvcConfigurer接口bean的configureHandlerExceptionResolvers方法,系统的都是空方法
     5         configureHandlerExceptionResolvers(exceptionResolvers);
     6         if (exceptionResolvers.isEmpty()) {
     7             //我们的在这里才添加,我们看看这个方法
     8             addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
     9         }
    10         extendHandlerExceptionResolvers(exceptionResolvers);
    11         HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
    12         composite.setOrder(0);
    13         composite.setExceptionResolvers(exceptionResolvers);
    14         return composite;
    15     }
    org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport
     1     protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers,
     2             ContentNegotiationManager mvcContentNegotiationManager) {
     3 
     4         ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();
     5         exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager);
     6         exceptionHandlerResolver.setMessageConverters(getMessageConverters());
     7         exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());
     8         exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());
     9         if (jackson2Present) {
    10             exceptionHandlerResolver.setResponseBodyAdvice(
    11                     Collections.singletonList(new JsonViewResponseBodyAdvice()));
    12         }
    13         if (this.applicationContext != null) {
    14             exceptionHandlerResolver.setApplicationContext(this.applicationContext);
    15         }
    16         //上面的 都是设置的属性,跟我们没啥大关系,主要在这里进行的添加自定义异常处理
    17         exceptionHandlerResolver.afterPropertiesSet();
    18         exceptionResolvers.add(exceptionHandlerResolver);
    19 
    20         ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();
    21         responseStatusResolver.setMessageSource(this.applicationContext);
    22         exceptionResolvers.add(responseStatusResolver);
    23 
    24         exceptionResolvers.add(new DefaultHandlerExceptionResolver());
    25     }
    org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport

        最主要的初始化过程在这里,从这些代码中就可以看到为什么我们自定义异常需要进行使用@ControllerAdvice,并且方法使用@ExceptionHandler(Exception.class)注解了

     1     @Override
     2     public void afterPropertiesSet() {
     3         // Do this first, it may add ResponseBodyAdvice beans
     4         //走这里初始化,添加
     5         initExceptionHandlerAdviceCache();
     6 
     7         if (this.argumentResolvers == null) {
     8             List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
     9             this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    10         }
    11         if (this.returnValueHandlers == null) {
    12             List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
    13             this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    14         }
    15     }
    16 
    17 
    18     org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java
    19     private void initExceptionHandlerAdviceCache() {
    20         if (getApplicationContext() == null) {
    21             return;
    22         }
    23         //看到这里基本就知道啥意思了,找出带有@ControllerAdvice的注解bean
    24         List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
    25         for (ControllerAdviceBean adviceBean : adviceBeans) {
    26             Class<?> beanType = adviceBean.getBeanType();
    27             if (beanType == null) {
    28                 throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
    29             }
    30             //找出当前bean的异常处理方法
    31             ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
    32             if (resolver.hasExceptionMappings()) {
    33                 this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
    34             }
    35             if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
    36                 this.responseBodyAdvice.add(adviceBean);
    37             }
    38         }
    39 
    40         if (logger.isDebugEnabled()) {
    41             int handlerSize = this.exceptionHandlerAdviceCache.size();
    42             int adviceSize = this.responseBodyAdvice.size();
    43             if (handlerSize == 0 && adviceSize == 0) {
    44                 logger.debug("ControllerAdvice beans: none");
    45             }
    46             else {
    47                 logger.debug("ControllerAdvice beans: " +
    48                         handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");
    49             }
    50         }
    51     }
    org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver

      找到类后,是如何找到方法的呢?主要看如何创建ExceptionHandlerMethodResolver的过程。

     1 public ExceptionHandlerMethodResolver(Class<?> handlerType) {
     2     //EXCEPTION_HANDLER_METHODS的定义:
     3     //public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
     4     //            AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);
     5     //所以他会寻找带有ExceptionHandler注解的方法
     6         for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
     7             //寻找方法注解上配置的捕获的异常类,并添加,如果有两个方法都对一个异常进行自定义处理了,怎么办呢。
     8             for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
     9                 //他会出异常的。不过前提是同一个类里,不同类对同一个异常进行自定义的话,谁在前面就有谁来处理
    10                 addExceptionMapping(exceptionType, method);
    11             }
    12         }
    13     }
    org/springframework/web/method/annotation/ExceptionHandlerMethodResolver

      添加自定义异常的时候抛异常是在这里

    1     private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
    2         Method oldMethod = this.mappedMethods.put(exceptionType, method);
    3         //在这里,已经显示出来了,博主就不试了
    4         if (oldMethod != null && !oldMethod.equals(method)) {
    5             throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
    6                     exceptionType + "]: {" + oldMethod + ", " + method + "}");
    7         }
    8     }
    org/springframework/web/method/annotation/ExceptionHandlerMethodResolver

      好了。所有异常添加完毕了,我们来测试一下异常来的时候,Springboot是如何选择自定义异常并返回的,我们上面所有的操作都是在创建HandlerExceptionResolver时进行的,为什么要添加到HandlerExceptionResolver这里呢?看一下代码:

     1 //第一次请求进来时,会先查找是否有自定义异常,如果有的话添加,没有记录日志就完了
     2     private void initHandlerExceptionResolvers(ApplicationContext context) {
     3         this.handlerExceptionResolvers = null;
     4 
     5         if (this.detectAllHandlerExceptionResolvers) {
     6             // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
     7                         //这里会在beanfactroy中查找到HandlerExceptionResolver类,刚才初始化的时候,我们所有的自定义异常都在里面 
     8             Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
     9                     .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
    10             if (!matchingBeans.isEmpty()) {
    11                 this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
    12                 // We keep HandlerExceptionResolvers in sorted order.
    13                 AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
    14             }
    15         }
    16         else {
    17             try {
    18                 HandlerExceptionResolver her =
    19                         context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
    20                 this.handlerExceptionResolvers = Collections.singletonList(her);
    21             }
    22             catch (NoSuchBeanDefinitionException ex) {
    23                 // Ignore, no HandlerExceptionResolver is fine too.
    24             }
    25         }
    26 
    27         // Ensure we have at least some HandlerExceptionResolvers, by registering
    28         // default HandlerExceptionResolvers if no other resolvers are found.
    29         if (this.handlerExceptionResolvers == null) {
    30             this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
    31             if (logger.isTraceEnabled()) {
    32                 logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +
    33                         "': using default strategies from DispatcherServlet.properties");
    34             }
    35         }
    36     }            
    org/springframework/web/servlet/DispatcherServlet

      走完初始化,经过过滤器,拦截器终于到了我们的请求方法,我们的方法还报错了,所以会走到异常中,我们DispatcherServlet会进行抓住异常,然后回调用我们的processDispatchResult方法,大家可以自己看一下org/springframework/web/servlet/DispatcherServlet.java的源码,然后我们来分析一下这个方法都干啥了吧

     1     private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
     2             @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
     3             @Nullable Exception exception) throws Exception {
     4 
     5         boolean errorView = false;
     6 
     7         if (exception != null) {
     8             if (exception instanceof ModelAndViewDefiningException) {
     9                 logger.debug("ModelAndViewDefiningException encountered", exception);
    10                 mv = ((ModelAndViewDefiningException) exception).getModelAndView();
    11             }
    12             else {
    13                 Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
    14                 //如果请求方法有异常,则进行处理,并返回ModelAndView
    15                 mv = processHandlerException(request, response, handler, exception);
    16                 errorView = (mv != null);
    17             }
    18         }
    19     .........
    20     }
    org/springframework/web/servlet/DispatcherServlet.java

      那Springboot是如何选择哪一个是符合条件的自定义异常处理呢?如果我们定义了两个处理类,都对同一个异常进行捕获并返回不一样的信息咋办呢?看源码吧

     1 //这里会选择符合条件的自定义异常
     2     protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
     3             @Nullable HandlerMethod handlerMethod, Exception exception) {
     4 
     5         Class<?> handlerType = null;
     6 
     7         if (handlerMethod != null) {
     8             // Local exception handler methods on the controller class itself.
     9             // To be invoked through the proxy, even in case of an interface-based proxy.
    10             handlerType = handlerMethod.getBeanType();
    11             ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
    12             if (resolver == null) {
    13                 resolver = new ExceptionHandlerMethodResolver(handlerType);
    14                 this.exceptionHandlerCache.put(handlerType, resolver);
    15             }
    16             Method method = resolver.resolveMethod(exception);
    17             if (method != null) {
    18                 return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
    19             }
    20             // For advice applicability check below (involving base packages, assignable types
    21             // and annotation presence), use target class instead of interface-based proxy.
    22             if (Proxy.isProxyClass(handlerType)) {
    23                 handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
    24             }
    25         }
    26         //exceptionHandlerAdviceCache这个map是我们添加 的自定义异常
    27         for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
    28             ControllerAdviceBean advice = entry.getKey();
    29             //这个判断条件是查看是否有符合条件的自定义异常,如果有两个的话,
    30             if (advice.isApplicableToBeanType(handlerType)) {
    31                 ExceptionHandlerMethodResolver resolver = entry.getValue();
    32                 Method method = resolver.resolveMethod(exception);
    33                 if (method != null) {
    34                     return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
    35                 }
    36             }
    37         }
    38 
    39         return null;
    40     }
    org/springframework/web/servlet/DispatcherServlet.java

      逻辑基本是上面的,但是真正处理是否符合是在这里的一个方法中:

     1 public boolean isApplicableToBeanType(@Nullable Class<?> beanType) {
     2         return this.beanTypePredicate.test(beanType);
     3     }
     4     public boolean test(Class<?> controllerType) {
     5          ///默认不配的其他属性的时候是返回true的,就是对所有包下的异常都适用
     6         if (!hasSelectors()) {
     7             return true;
     8         }
     9         else if (controllerType != null) {
    10             //我们的@ControllerAdvice注解是有basePackages属性的,只有匹配成功才会返回,否则就算自定义异常想要捕获,不在捕获包范围下不管该异常
    11             for (String basePackage : this.basePackages) {
    12                 if (controllerType.getName().startsWith(basePackage)) {
    13                     return true;
    14                 }
    15             }
    16             for (Class<?> clazz : this.assignableTypes) {
    17                 if (ClassUtils.isAssignable(clazz, controllerType)) {
    18                     return true;
    19                 }
    20             }
    21             for (Class<? extends Annotation> annotationClass : this.annotations) {
    22                 if (AnnotationUtils.findAnnotation(controllerType, annotationClass) != null) {
    23                     return true;
    24                 }
    25             }
    26         }
    27         return false;
    28     }
    org/springframework/web/method/ControllerAdviceBean

      到这里基本如何写自定义异常、以及为什么这么写、底层做了哪些判断都已经讲解完了,自定义异常在工作中还是非常常用的一种手段,因为我们不可能暴露出我们内部的错误信息直接返回给用户,不仅用户体验不好,并且安全性也极其差。

      原创不易,转载请说明出处:https://www.cnblogs.com/guoxiaoyu/p/13489565.html


  • 相关阅读:
    Prototype源码浅析——String部分(四)之补充
    改造alert的引发的争论【基本类型与引用类型】
    Eclipse rcp 窗口激活
    Eclipse statusLine 加入进度信息
    线的匹配
    python 文本搜索
    Eclipse rcp 编辑器行号显示
    CDT重建索引
    Eclipse rcp 数据存储
    CTabFolder 最大最小化
  • 原文地址:https://www.cnblogs.com/guoxiaoyu/p/13489565.html
Copyright © 2011-2022 走看看