• spring编程常见错误2 (学习笔记)


    spring boot web 相关

    spring boot 如何支持http请求的

    @RestController
    public class HelloWorldController {
        @RequestMapping(path = "hi", method = RequestMethod.GET)
        public String hi(){
             return "helloworld";
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    首先,解析 HTTP 请求。对于 Spring 而言,它本身并不提供通信层的支持,它是依赖于 Tomcat、Jetty 等容器来完成通信层的支持,
    在这里插入图片描述
    如何启动:SpringApplication.run(Application.class, args);
    在这里插入图片描述
    那为什么使用的是 Tomcat? @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class }) 生效

    //org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration
    
    class ServletWebServerFactoryConfiguration {
    
       @Configuration(proxyBeanMethods = false)
       @ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
       @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
       public static class EmbeddedTomcat {
          @Bean
          public TomcatServletWebServerFactory tomcatServletWebServerFactory(
             //省略非关键代码
             return factory;
          }
    
       }
       
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
    @ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
    public static class EmbeddedJetty {
       @Bean
       public JettyServletWebServerFactory JettyServletWebServerFactory(
             ObjectProvider serverCustomizers) {
           //省略非关键代码
          return factory;
       }
    }
    
    //省略其他容器配置
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    当一个 HTTP 请求访问时,会触发 Tomcat 底层提供的 NIO 通信来完成数据的接收,这点我们可以从下面的代码(org.apache.tomcat.util.net.NioEndpoint.Poller#run)中看出来:

    @Override
    public void run() {
        while (true) {
             //省略其他非关键代码
             //轮询注册的兴趣事件
             if (wakeupCounter.getAndSet(-1) > 0) {
                   keyCount = selector.selectNow();
             } else {
                   keyCount = selector.select(selectorTimeout);
     
            //省略其他非关键代码
            Iterator iterator =
                keyCount > 0 ? selector.selectedKeys().iterator() : null;
    
            while (iterator != null && iterator.hasNext()) {
                SelectionKey sk = iterator.next();
                NioSocketWrapper socketWrapper = (NioSocketWrapper)  
                //处理事件
                processKey(sk, socketWrapper);
                //省略其他非关键代码
               
            }
           //省略其他非关键代码
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    上述代码会完成请求事件的监听和处理,最终在 processKey 中把请求事件丢入线程池去处理。请求事件的接收具体调用栈如下:
    在这里插入图片描述
    线程池对这个请求的处理的调用栈如下:
    在这里插入图片描述
    在上述调用中,最终会进入 Spring Boot 的处理核心,即 DispatcherServlet(上述调用栈没有继续截取完整调用,所以未显示)。可以说,DispatcherServlet 是用来处理 HTTP 请求的中央调度入口程序,为每一个 Web 请求映射一个请求的处理执行体(API controller/method)。

    javax.servlet.http.HttpServlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
    
    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
          doDispatch(request, response);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 分发:
      RequestMappingHandlerMapping
      在这里插入图片描述
    2. 执行
      org.springframework.web.method.support.InvocableHandlerMethod#doInvoke
      在这里插入图片描述
      然后将解析后的http请求的处理流程

    综上

    1. 需要有一个地方(例如 Map)去维护从 HTTP path/method 到具体执行方法的映射;
    2. 当一个请求来临时,根据请求的关键信息来获取对应的需要执行的方法;
    3. 根据方法定义解析出调用方法的参数值,然后通过反射调用方法,获取返回结果。
      伪代码:
    public class HttpRequestHandler{
        
        Map mapper = new HashMap<>();
        
        public Object handle(HttpRequest httpRequest){
             RequestKey requestKey = getRequestKey(httpRequest);         
             Method method = this.mapper.getValue(requestKey);
             Object[] args = resolveArgsAccordingToMethod(httpRequest, method);
             return method.invoke(controllerObject, args);
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    URL 匹配方法基本步骤

    AbstractHandlerMethodMapping#lookupHandlerMethod
    
    
    @Nullable
    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
       List matches = new ArrayList<>();
       //尝试按照 URL 进行精准匹配
       List directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
       if (directPathMatches != null) {
          //精确匹配上,存储匹配结果
          addMatchingMappings(directPathMatches, matches, request);
       }
       if (matches.isEmpty()) {
          //没有精确匹配上,尝试根据请求来匹配
          addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
       }
    
       if (!matches.isEmpty()) {
          Comparator comparator = new MatchComparator(getMappingComparator(request));
          matches.sort(comparator);
          Match bestMatch = matches.get(0);
          if (matches.size() > 1) {
             //处理多个匹配的情况
          }
          //省略其他非关键代码
          return bestMatch.handlerMethod;
       }
       else {
          //匹配不上,直接报错
          return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    Required String parameter xxxx is not present

    1. 设置 @RequestParam 的默认值,@RequestParam(value = “address”, defaultValue = “no address”) String address
    2. 设置 @RequestParam 的 required 值 @RequestParam(value = “address”, required = false) String address)
    3. 标记任何名为 Nullable 且 RetentionPolicy 为 RUNTIME 的注解 //org.springframework.lang.Nullable @RequestParam(value = “address”) @Nullable String address
    4. 修改参数类型为 Optional @RequestParam(value = “address”) Optionaladdress

    String 到 Date转换参数

    默认为 new Date()方法的Sat, 12 Aug 1995 13:30:00 GMT
    应该使用更好的@DateTimeFormat(pattern=“yyyy-MM-dd HH:mm:ss”)

    同名 header 的解析,使用Map类型覆盖问题

    GET http://localhost:8080/hi1
    myheader: h1
    myheader: h2

    方法1:
    @RequestHeader(“myHeaderName”) String name
    方法2:
    @RequestHeader() Map map

    结论:
    应该使用MutiValueMap
    //方式 1@RequestHeader() MultiValueMap map//
    方式 2@RequestHeader() HttpHeaders map
    方式 2 更值得推荐,因为它使用了大多数人常用的 Header 获取方法,例如获取 Content-Type 直接调用它的 getContentType() 即可,诸如此类,非常好用。

    在 HTTP 协议规定中,Header 的名称是无所谓大小写的。但是这并不意味着所有能获取到 Header 的途径,最终得到的 Header 名称都是统一大小写的

    不是所有的 Header 在响应中都能随意指定,虽然表面看起来能生效,但是最后返回给客户端的仍然不是你指定的值。例如,在 Tomcat 下,CONTENT_TYPE 这个 Header 就是这种情况。

    @Validated 与 @Vaild

    结论:

    1. @Validated 不能对字段添加,请使用@Valid 注解
    2. 只要以Valid开头的注解,都会触发字段的校验
    private CascadingMetaDataBuilder getCascadingMetaData(Type type, AnnotatedElement annotatedElement,
          Map, CascadingMetaDataBuilder> containerElementTypesCascadingMetaData) {
       return CascadingMetaDataBuilder.annotatedObject( type, annotatedElement.isAnnotationPresent( Valid.class ), containerElementTypesCascadingMetaData,
                   getGroupConversions( annotatedElement ) );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    @WebFilter 注册的Bean无法从spring上下文中,通过原本类型获取到

    实现

    1. 继承Filter 并添加@WebFilter 注解
    @WebFilter
    @Slf4j
    public class TimeCostFilter implements Filter {
        public TimeCostFilter(){
            System.out.println("construct");
        }
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            log.info("开始计算接口耗时");
            long start = System.currentTimeMillis();
            chain.doFilter(request, response);
            long end = System.currentTimeMillis();
            long time = end - start;
            System.out.println("执行时间(ms):" + time);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    1. 启动程序,添加 @ServletComponentScan 注解
    @SpringBootApplication
    @ServletComponentScan
    @Slf4j
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
            log.info("启动成功");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    问题点,添加@WebFilter 注解的Bean 会作为一个InnerBean被实例化作为FilterRegistrationBean并注册到相应 Spring 容器中,并不会将自身作为 Bean 注册到spring 容器中。

    解决:

    @Controller
    @Slf4j
    public class StudentController {
        @Autowired
        @Qualifier("com.spring.puzzle.filter.TimeCostFilter")
        ​FilterRegistrationBean timeCostFilter;
     
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    FilterRegistrationBean 是什么?它是如何被定义的?

    实际上,WebFilter 的全名是 javax.servlet.annotation.WebFilter,很明显,它并不属于 Spring,而是 Servlet 的规范。当 Spring Boot 项目中使用它时,Spring Boot 使用了 org.springframework.boot.web.servlet.FilterRegistrationBean 来包装 @WebFilter 标记的实例。从实现上来说,即 FilterRegistrationBean#Filter 属性就是 @WebFilter 标记的实例。

    @WebFilter 这个注解是如何被处理的

    @WebFilter 的处理是在 Spring Boot 启动时,而处理的触发点是 ServletComponentRegisteringPostProcessor 这个类。它继承了 BeanFactoryPostProcessor 接口,实现对 @WebFilter、@WebListener、@WebServlet 的扫描和处理,其中对于 @WebFilter 的处理使用的就是上文中提到的 WebFilterHandler。这个逻辑可以参考下面的关键代码:

    
    class ServletComponentRegisteringPostProcessor implements BeanFactoryPostProcessor, ApplicationContextAware {
       private static final List HANDLERS;
       static {
          List servletComponentHandlers = new ArrayList<>();
          servletComponentHandlers.add(new WebServletHandler());
          servletComponentHandlers.add(new WebFilterHandler());
          servletComponentHandlers.add(new WebListenerHandler());
          HANDLERS = Collections.unmodifiableList(servletComponentHandlers);
       }
       // 省略非关键代码
       @Override
       public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
          if (isRunningInEmbeddedWebServer()) {
             ClassPathScanningCandidateComponentProvider componentProvider = createComponentProvider();
             for (String packageToScan : this.packagesToScan) {
                scanPackage(componentProvider, packageToScan);
             }
          }
       }
       
      private void scanPackage(ClassPathScanningCandidateComponentProvider componentProvider, String packageToScan) {
         // 扫描注解
         for (BeanDefinition candidate : componentProvider.findCandidateComponents(packageToScan)) {
            if (candidate instanceof AnnotatedBeanDefinition) {
               // 使用 WebFilterHandler 等进行处理
               for (ServletComponentHandler handler : HANDLERS) {
                  handler.handle(((AnnotatedBeanDefinition) candidate),
                        (BeanDefinitionRegistry) this.applicationContext);
               }
            }
         }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    WebServletHandler 通过父类 ServletComponentHandler 的模版方法模式,处理了所有被 @WebFilter 注解的类

    public void doHandle(Map attributes, AnnotatedBeanDefinition beanDefinition,
          BeanDefinitionRegistry registry) {
       BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(FilterRegistrationBean.class);
       builder.addPropertyValue("asyncSupported", attributes.get("asyncSupported"));
       builder.addPropertyValue("dispatcherTypes", extractDispatcherTypes(attributes));
       builder.addPropertyValue("filter", beanDefinition);
       //省略其他非关键代码
       builder.addPropertyValue("urlPatterns", extractUrlPatterns(attributes));
       registry.registerBeanDefinition(name, builder.getBeanDefinition());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    Filter 中不小心多次执行 doFilter()

    如果我们需要构建的过滤器是针对全局路径有效,且没有任何特殊需求(主要是指对 Servlet 3.0 的一些异步特性支持),那么你完全可以直接使用 Filter 接口(或者继承 Spring 对 Filter 接口的包装类 OncePerRequestFilter),并使用 @Component 将其包装为 Spring 中的普通 Bean,也是可以达到预期的需求。

    @Component
    public class DemoFilter implements Filter {
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            try {
                //模拟异常
                System.out.println("Filter 处理中时发生异常");
                throw new RuntimeException();
            } catch (Exception e) {
                chain.doFilter(request, response);
            }
            chain.doFilter(request, response);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    1. 当一个请求来临时,会执行到 StandardWrapperValve 的 invoke(),这个方法会创建 ApplicationFilterChain,并通过 ApplicationFilterChain#doFilter() 触发过滤器执行;
    2. ApplicationFilterChain 的 doFilter() 会执行其私有方法 internalDoFilter;
    3. 在 internalDoFilter 方法中获取下一个 Filter,并使用 request、response、this(当前 ApplicationFilterChain 实例)作为参数来调用 doFilter():
      public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException;
    4. 在 Filter 类的 doFilter() 中,执行 Filter 定义的动作并继续传递,获取第三个参数 ApplicationFilterChain,并执行其 doFilter();
      此时会循环执行进入第 2 步、第 3 步、第 4 步,直到第 3 步中所有的 Filter 类都被执行完毕为止;
    5. 所有的 Filter 过滤器都被执行完毕后,会执行 servlet.service(request, response) 方法,最终调用对应的 Controller 层方法 。
      在这里插入图片描述

    @Order 注解无法为@WebFilter 指定顺序的问题

    将@WebFilter 注解生成的Bean包装成FilterRegistrationBean没有设置order
    故解决方案:

    
    @Configuration
    public class FilterConfiguration {
        @Bean
        public FilterRegistrationBean authFilter() {
            FilterRegistrationBean registration = new FilterRegistrationBean();
            registration.setFilter(new AuthFilter());
            registration.addUrlPatterns("/*");
            registration.setOrder(2);
            return registration;
        }
    
        @Bean
        public FilterRegistrationBean timeCostFilter() {
            FilterRegistrationBean registration = new FilterRegistrationBean();
            registration.setFilter(new TimeCostFilter());
            registration.addUrlPatterns("/*");
            registration.setOrder(1);
            return registration;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    或使用@Component + 实现Filter接口注入上下文即可

    执行器顺序 与 重复执行问题

    RegistrationBean 中 order 属性的值 ->ServletContextInitializerBeans 类成员变量 sortedList 中元素的顺序 ->ServletWebServerApplicationContext 中 selfInitialize() 遍历 FilterRegistrationBean 的顺序 ->addFilterMapBefore() 调用的顺序 ->filterMaps 内元素的顺序 ->过滤器的执行顺序

    
    public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
          Class... initializerTypes) {
       this.initializers = new LinkedMultiValueMap<>();
       this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
             : Collections.singletonList(ServletContextInitializer.class);
       addServletContextInitializerBeans(beanFactory);
       addAdaptableBeans(beanFactory);
       List sortedInitializers = this.initializers.values().stream()
             .flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
             .collect(Collectors.toList());
       this.sortedList = Collections.unmodifiableList(sortedInitializers);
       logMappings(this.initializers);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    而第 7 行的 addAdaptableBeans(),其作用则是实例化所有实现 Filter 接口的类(严格说,是实例化并注册了所有实现 Servlet、Filter 以及 EventListener 接口的类),然后再逐一包装为 FilterRegistrationBean。

    
    protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
       // 省略非关键代码
       addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());
       // 省略非关键代码
    }
    
    
    private  void addAsRegistrationBean(ListableBeanFactory beanFactory, Class type,
          Class beanType, RegistrationBeanAdapter adapter) {
       List> entries = getOrderedBeansOfType(beanFactory, beanType, this.seen);
       for (Entry entry : entries) {
          String beanName = entry.getKey();
          B bean = entry.getValue();
          if (this.seen.add(bean)) {
             // One that we haven't already seen
             RegistrationBean registration = adapter.createRegistrationBean(beanName, bean, entries.size());
             int order = getOrder(bean);
             registration.setOrder(order);
             this.initializers.add(type, registration);
             if (logger.isTraceEnabled()) {
                logger.trace("Created " + type.getSimpleName() + " initializer for bean '" + beanName + "'; order="
                      + order + ", resource=" + getResourceDescription(beanName, beanFactory));
             }
          }
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    1. 通过 getOrderedBeansOfType() 创建了所有 Filter 子类的实例,即所有实现 Filter 接口且被 @Component 修饰的类;
    2. 依次遍历这些 Filter 类实例,并通过 RegistrationBeanAdapter 将这些类包装为 RegistrationBean;
    3. 获取 Filter 类实例的 Order 值,并设置到包装类 RegistrationBean 中;
    4. 将 RegistrationBean 添加到 this.initializers。
      结论:
    5. @WebFilter 会让 addServletContextInitializerBeans() 实例化,并注册所有动态生成的 FilterRegistrationBean 类型的过滤器;
    6. @Component 会让 addAdaptableBeans() 实例化所有实现 Filter 接口的类,然后再逐一包装为 FilterRegistrationBean 类型的过滤器。

    @WebFilter 和 @Component 的相同点是:

    1. 它们最终都被包装并实例化成为了 FilterRegistrationBean;
    2. 它们最终都是在 ServletContextInitializerBeans 的构造器中开始被实例化。

    @WebFilter 和 @Component 的不同点是:

    1. 被 @WebFilter 修饰的过滤器会被提前在 BeanFactoryPostProcessors 扩展点包装成 FilterRegistrationBean 类型的 BeanDefinition,然后在 ServletContextInitializerBeans.addServletContextInitializerBeans() 进行实例化;而使用 @Component 修饰的过滤器类,是在 ServletContextInitializerBeans.addAdaptableBeans() 中被实例化成 Filter 类型后,再包装为 RegistrationBean 类型。
    2. 被 @WebFilter 修饰的过滤器不会注入 Order 属性,但被 @Component 修饰的过滤器会在 ServletContextInitializerBeans.addAdaptableBeans() 中注入 Order 属性。

    结论:
    使用@Component + @Order + 实现Filter接口,比使用@WebFilter + @ServletComponentScan 好使

    @ExceptionHandler() 不会对过滤器异常进行拦截

    在这里插入图片描述
    当所有的过滤器被执行完毕以后,Spring 才会进入 Servlet 相关的处理,而 DispatcherServlet 才是整个 Servlet 处理的核心,提供 Spring Web MVC 的集中访问点并负责职责的分派。正是在这里,Spring 处理了请求和处理器之间的对应关系,包含统一异常处理。

    首先我们来了解下 ControllerAdvice 是如何被 Spring 加载并对外暴露的。

    在 Spring Web 的核心配置类 WebMvcConfigurationSupport 中,被 @Bean 修饰的 handlerExceptionResolver(),会调用 addDefaultHandlerExceptionResolvers() 来添加默认的异常解析器。

    @Bean
    public HandlerExceptionResolver handlerExceptionResolver(
          @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
       List 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
    ->
    createExceptionHandlerExceptionResolver();
    ->
    new ExceptionHandlerExceptionResolver()
    -> 
    public void afterPropertiesSet() {
    ->
    initExceptionHandlerAdviceCache();
    
    
    private void initExceptionHandlerAdviceCache() {
       //省略非关键代码
       List adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
       for (ControllerAdviceBean adviceBean : adviceBeans) {
          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);
          }
     //省略非关键代码
    }
    initExceptionHandlerAdviceCache() 中完成了所有 ControllerAdvice 中的 ExceptionHandler 的初始化。其具体操作,就是查找所有 @ControllerAdvice 注解的 Bean,把它们放到成员变量 exceptionHandlerAdviceCache 中
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    1. WebMvcConfigurationSupport 中的 handlerExceptionResolver() 实例化
    2. 并注册了一个 ExceptionHandlerExceptionResolver 的实例,而所有被 3. @ControllerAdvice 注解修饰的异常处理器,都会在 ExceptionHandlerExceptionResolver 实例化的时候自动扫描并装载在其类成员变量 exceptionHandlerAdviceCache 中。
    3. 当第一次请求发生时,DispatcherServlet 中的 initHandlerExceptionResolvers() 将获取所有注册到 Spring 的 HandlerExceptionResolver 类型的实例,而 ExceptionHandlerExceptionResolver 恰好实现了 HandlerExceptionResolver 接口,这些 HandlerExceptionResolver 类型的实例则会被写入到类成员变量 handlerExceptionResolvers 中。
    private void initHandlerExceptionResolvers(ApplicationContext context) {
       this.handlerExceptionResolvers = null;
    
       if (this.detectAllHandlerExceptionResolvers) {
          // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
          Map matchingBeans = BeanFactoryUtils
                .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
          if (!matchingBeans.isEmpty()) {
             this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
             // We keep HandlerExceptionResolvers in sorted order.
             AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
          }
          //省略非关键代码
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    ControllerAdvice 是如何被 Spring 消费并处理异常的

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
       //省略非关键代码
    
       try {
          ModelAndView mv = null;
          Exception dispatchException = null;
          try {
             //省略非关键代码
             //查找当前请求对应的 handler,并执行
             //省略非关键代码
          }
          catch (Exception ex) {
             dispatchException = ex;
          }
          catch (Throwable err) {
             dispatchException = new NestedServletException("Handler dispatch failed", err);
          }
          processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
       }
       //省略非关键代码
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    简述 doDispatch() -> processDispatchResult() -> processHandlerException()

    
    protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
          @Nullable Object handler, Exception ex) throws Exception {
       //省略非关键代码
       ModelAndView exMv = null;
       if (this.handlerExceptionResolvers != null) {
          for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
             exMv = resolver.resolveException(request, response, handler, ex);
             if (exMv != null) {
                break;
             }
          }
       }
       //省略非关键代码
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    特殊的 404 异常,不会被ExceptionHandler处理

    原因:
    DispatcherServlet#doDispatch()

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
            //省略非关键代码
             mappedHandler = getHandler(processedRequest);
             if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
             }
             //省略非关键代码
    }
    
    	@Nullable
    	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    		if (this.handlerMappings != null) {
    			for (HandlerMapping mapping : this.handlerMappings) {
    				HandlerExecutionChain handler = mapping.getHandler(request);
    				if (handler != null) {
    					return handler;
    				}
    			}
    		}
    		return null;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    实际上mappedHandler 有2个默认的 /**拦截路径 与 /webjars/** 的handler会被初始化存在,
    所以最终在 doDispatch() 的 getHandler() 将会获取到此 handler,从而 mappedHandler==null 条件不能得到满足,因而无法走到 noHandlerFound(),不会抛出 NoHandlerFoundException 异常,进而无法被后续的异常处理器进一步处理。

    DispatcherServlet 类中的 doDispatch() 是整个 Servlet 处理的核心,它不仅实现了请求的分发,也提供了异常统一处理等等一系列功能;
    WebMvcConfigurationSupport 是 Spring Web 中非常核心的一个配置类,无论是异常处理器的包装注册(HandlerExceptionResolver),还是资源处理器的包装注册(SimpleUrlHandlerMapping),都是依靠这个类来完成的。

    解决方案1:
    不初始化 2个默认的 /**拦截路径 与 /webjars/** 的handler,且让NoHandlerFoundException 抛出

    spring.resources.add-mappings=false
    spring.mvc.throwExceptionIfNoHandlerFound=true
    
    • 1
    • 2
    protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
       if (this.throwExceptionIfNoHandlerFound) {
          throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
                new ServletServerHttpRequest(request).getHeaders());
       }
       else {
          response.sendError(HttpServletResponse.SC_NOT_FOUND);
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
       if (!this.resourceProperties.isAddMappings()) {
          logger.debug("Default resource handling disabled");
          return;
       }
       Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
       CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
       if (!registry.hasMappingForPattern("/webjars/**")) {
          customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/")
                .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
       }
       String staticPathPattern = this.mvcProperties.getStaticPathPattern();
       if (!registry.hasMappingForPattern(staticPathPattern)) {
          customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
                .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
                .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    GET请求相同参数传递会怎么样

    结论,会用使用,分隔的字符串进行拼接传入
    org.apache.tomcat.util.http.Parameters#addParameter

    @RequestMapping(path = "/hi2", method = RequestMethod.GET)
    public String hi2(@RequestParam("name") String name){
        return name;
    };
    
    public void addParameter( String key, String value )
            throws IllegalStateException {
        //省略其他非关键代码
        ArrayList values = paramHashValues.get(key);
        if (values == null) {
            values = new ArrayList<>(1);
            paramHashValues.put(key, values);
        }
        values.add(value);
    }
    
    public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
       return this.helperConverter.convert(Arrays.asList(ObjectUtils.toObjectArray(source)), sourceType, targetType);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    spring-boot 如何整合 tomcat

    SpringApplication.run(Application.class, args);
    
    • 1
    @Override
    protected void onRefresh() {
       super.onRefresh();
       try {
          createWebServer();
       }
       catch (Throwable ex) {
          throw new ApplicationContextException("Unable to start web server", ex);
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    private void createWebServer() {
       WebServer webServer = this.webServer;
       ServletContext servletContext = getServletContext();
       if (webServer == null && servletContext == null) {
          ServletWebServerFactory factory = getWebServerFactory();
          this.webServer = factory.getWebServer(getSelfInitializer());
       }
       // 省略非关键代码
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    factory.getWebServer() 会启动 Tomcat,其中这个方法调用传递了参数 getSelfInitializer(),它返回的是一个特殊格式回调方法 this::selfInitialize 用来添加 Filter 等,它是当 Tomcat 启动后才调用的

    private void selfInitialize(ServletContext servletContext) throws ServletException {
       prepareWebApplicationContext(servletContext);
       registerApplicationScope(servletContext);
       WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
       for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
          beans.onStartup(servletContext);
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 启动 Spring Boot 时,启动 Tomcat:
      在这里插入图片描述
    2. Tomcat 启动后回调 selfInitialize
      3
  • 相关阅读:
    ES索引误删的名场面
    Oracle数据库面试题-10
    【作为网工,你还只会Wireshark?那你就OUT了!】
    QT(37)-mosquitto-MQTT客户端
    Math.Round() “四舍五入“方法
    LeetCode算法心得——找到冠军(反向推理)
    数据结构与算法系列-二分查找
    获取远程仓库的信息和远程分支的信息
    v-model绑定input、textarea、checkbox、radio、select
    AcWing_11. 背包问题求方案数_dp
  • 原文地址:https://blog.csdn.net/hesiyuanlx/article/details/126658544