自学笔记记录:Spring Boot 2.5.0源码学习【1、静态资源配置原理】

【“没有什么是一个断点不能解决的”系列】

一、Spring Boot静态资源配置原理

  • Spring Boot 启动默认加载 XxxxxAutoConfiguration 类(自动配置类)

  • SpringMVC 功能的自动配置类 主要是 WebMvcAutoConfiguration,检查 WebMvcAutoConfiguration 配置类是否生效

    // WebMvcAutoConfiguration.class
    
    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnWebApplication(
        type = Type.SERVLET
    )
    @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
    @ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
    @AutoConfigureOrder(-2147483638)
    @AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class})
    public class WebMvcAutoConfiguration {}
    

    @Configuration 说明这是一个配置类;

    @ConditionalOnWebApplication(type = Type.SERVLET) 判断是不是一个 Servlet 应用,我们的 Spring Boot 应用就是一个典型的 Servlet 应用,因此生效;

    @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class}) 判断应用里面是否有 Servlet、DispatcherServlet、WebMvcConfigurer 这三个类型场景,Servlet类就是原生的Web开发类,DispatcherServlet、WebMvcConfigurer 都是导了 spring-webmvc-5.3.7.jar 包自然会有的类,因此生效;

    @ConditionalOnMissingBean({WebMvcConfigurationSupport.class}) 判断容器中没有 WebMvcConfigurationSupport 类型的组件时就会生效

  • WebMvcAutoConfiguration 配置类给容器中配了什么?

    主要来看 WebMvcAutoConfigurationAdapter 这个内部配置类

    // WebMvcAutoConfiguration.class
    
    @Configuration(proxyBeanMethods = false)
    @Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
    @EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class, WebProperties.class})
    @Order(0)
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {}
    

    注意留意@EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class, WebProperties.class})

    配置文件的相关属性和xxx进行了绑定,分别是 WebMvcProperties (点进去发现与 spring.mvc 进行了绑定)、 ResourceProperties(点进去发现与 spring.resources 进行了绑定)、WebProperties(点进去发现与 spring.web 进行了绑定),@EnableConfigurationProperties 注解帮我们按制定规则绑定好配置文件并且将它们导入容器中。

    1、额外注意配置类中只有一个有参构造器的情况

    另外注意,WebMvcAutoConfigurationAdapter 这个配置类中只有一个有参构造器,那么这个有参构造器中所有参数的值默认都会从容器中去找,

    • ResourceProperties resourceProperties:这个就是上面绑定了配置文件并且@EnableConfigurationProperties开启注入容器中的组件,所以这是获取和 spring.resources 绑定的所有的值的对象;
    • WebProperties webProperties:获取和 spring.web绑定的所有的值的对象;
    • WebMvcProperties mvcProperties:获取和 spring.mvc绑定的所有的值的对象;
    • ListableBeanFactory beanFactory:Spring 的 beanFactory,相当于找 Spring 容器;
    • ObjectProvider messageConvertersProvider:HttpMessageConverters,找到系统中所有的 HttpMessageConverters;
    • ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider:ResourceHandlerRegistrationCustomizer,相当于找到资源处理器的自定义器;
    • ObjectProvider dispatcherServletPath:DispatcherServletPath,相当于找 DispatchServlet 处理的路径;
    • ObjectProvider<ServletRegistrationBean<?>> servletRegistrations:ServletRegistrationBean,给应用注册原生的 Servlet、Filter 时用到…
    // WebMvcAutoConfiguration.class
    // public static class WebMvcAutoConfigurationAdapter 配置类中的构造器
    
    public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
        this.resourceProperties = (Resources)(resourceProperties.hasBeenCustomized() ? resourceProperties : webProperties.getResources());
        this.mvcProperties = mvcProperties;
        this.beanFactory = beanFactory;
        this.messageConvertersProvider = messageConvertersProvider;
        this.resourceHandlerRegistrationCustomizer = (WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
        this.dispatcherServletPath = dispatcherServletPath;
        this.servletRegistrations = servletRegistrations;
        this.mvcProperties.checkConfiguration();
    }
    

    2、资源处理的默认规则

    然后再来主要关注 addResourceHandlers(ResourceHandlerRegistry registry) 方法,添加资源处理器

    // WebMvcAutoConfiguration.class
    // public static class WebMvcAutoConfigurationAdapter
    
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        if (!this.resourceProperties.isAddMappings()) {
            logger.debug("Default resource handling disabled");
        } else {
            // webjars 的规则
            this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
            // 
            this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
                registration.addResourceLocations(this.resourceProperties.getStaticLocations());
                if (this.servletContext != null) {
                    ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
                    registration.addResourceLocations(new Resource[]{resource});
                }
    
            });
        }
    }
    
    1. 首先对 resourceProperties.isAddMappings() 进行判断,也就是配置文件绑定的 spring.resources.add-mappings 属性进行判断,如果该属性为 false 的话,那就进入报异常就不会进入 else 部分执行了,配置也就不再生效。因此我们可以在 application.properties 中对 spring.resources.add-mappings 属性设置为 false,相当于禁用掉所有静态资源的配置规则,就无法访问到静态资源。

    2. 在 spring.resources.add-mappings 属性值为 true 的默认情况下,进入 else 部分;

    3. 首先是 “/webjars/**” 请求路径的配置处理规则,写死了默认到 “/META-INF/resources/webjars/” 路径查找;

      // WebMvcAutoConfiguration.class
      // public static class WebMvcAutoConfigurationAdapter
      // public void addResourceHandlers(ResourceHandlerRegistry registry)
      
      this.addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
      

      访问 /webjars/ 下所有的请求,都会进入到 /META-INF/resources/webjars/ 路径下去查找。而且这个资源还能设置 spring.resources.cache.period 缓存时间,并且会缓存设置的相应时间,即访问过该路径一次之后,会在浏览器中缓存生效。缓存策略封装在 addResourceHandler 了,进入可以查看。

    4. 再是 静态资源请求路径 的配置规则,访问 spring.mvc.static-path-pattern 属性值的请求路径,默认是 “/**”,到 spring.web.resources.static-locations 属性值所在的路径下查找,默认是"classpath:/META-INF/resources/", “classpath:/resources/”, “classpath:/static/”, “classpath:/public/”;

      // WebMvcAutoConfiguration.class
      // public static class WebMvcAutoConfigurationAdapter
      // public void addResourceHandlers(ResourceHandlerRegistry registry)
      
      this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
        registration.addResourceLocations(this.resourceProperties.getStaticLocations());
        if (this.servletContext != null) {
            ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
            registration.addResourceLocations(new Resource[]{resource});
        }
      });
      

      this.mvcProperties.getStaticPathPattern() 获取到静态资源路径,可以发现 staticPathPattern 是在 WebMvcProperties 中的,而 WebMvcProperties 又是与 配置文件 spring.mvc 绑定的,相当于如果我们配了 spring.mvc.static-path-pattern,那么我们就会获取到对应的值,如果没有配,就是默认值 “/**”,任何访问 / 下的路径请求,就会去到this.resourceProperties.getStaticLocations() 路径下去查找,跟踪进入到 WebProperties 类中的 Resources 类,可以发现底层是配有默认静态路径的,分别是"classpath:/META-INF/resources/", “classpath:/resources/”, “classpath:/static/”, “classpath:/public/”,因此我们访问 / 路径请求静态资源,就会去到上面四个默认静态路径位置下查找。并且也有缓存策略,都封装在了 addResourceHandler 中了。

      (相较于Spring Boot 2.3.4版本不同的地方是,去掉了 spring.resources 的 Application Properties 配置属性,替换成了 spring.web.resources)

      // WebProperties.class
      
      public static class Resources {
        private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};
        private String[] staticLocations;
        private boolean addMappings;
        private boolean customized;
        private final WebProperties.Resources.Chain chain;
        private final WebProperties.Resources.Cache cache;
      
        public Resources() {
            this.staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
            this.addMappings = true;
            this.customized = false;
            this.chain = new WebProperties.Resources.Chain();
            this.cache = new WebProperties.Resources.Cache();
        }
      
        public String[] getStaticLocations() {
            return this.staticLocations;
        }
      }
      
    5. 以及 欢迎页 的配置规则,

      HandlerMapping:是 Spring MVC 中的一个核心组件,翻译为 处理器映射,保存了每一个 Handler 能处理哪些请求。找到各自的哪个请求由各自对应的哪个处理器处理,找到之后再利用反射机制调用能够处理的那个方法。

      此处 WelcomePageHandlerMapping 表示处理欢迎页请求的映射规则

      // WebMvcAutoConfiguration.class
      // public static class EnableWebMvcConfiguration
      
      @Bean
      public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
        WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
        welcomePageHandlerMapping.setInterceptors(this.getInterceptors(mvcConversionService, mvcResourceUrlProvider));
        welcomePageHandlerMapping.setCorsConfigurations(this.getCorsConfigurations());
        return welcomePageHandlerMapping;
      }
      

      同样这里 @Bean 给容器中注入 welcomePageHandlerMapping,这个方法中的传参也一定都会从容器中去获取。

      1. 此处 this.mvcProperties.getStaticPathPattern() 拿的仍然是配置文件中 静态路径配置属性(spring.mvc.static-path-pattern)的值或者是其默认值"/**";

      2. 进入到 WelcomePageHandlerMapping 构造器,具体检查其构造过程,

        WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) {
            if (welcomePage != null && "/**".equals(staticPathPattern)) {
                // 要使用欢迎页功能,必须是"/**"路径请求
                logger.info("Adding welcome page: " + welcomePage);
                this.setRootViewName("forward:index.html");
            } else if (this.welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) {
                // 否则调用 Controller,看谁能处理这个 /index 请求
                logger.info("Adding welcome page template: index");
                this.setRootViewName("index");
            }
        }
        

        从第一个 if 判断中我们可以看到 欢迎页需要存在 并且 静态路径配置属性(spring.mvc.static-path-pattern) 的值必须是 “/**”,我们才能使用欢迎页功能。

      3. 额外注意

        spring:
        mvc:
         static-path-pattern: /res/**
        #修改默认静态资源请求访问路径,会导致 欢迎页功能、favivon浏览器页面标签小图标功能 失效!原因是与上面第5步欢迎页配置同理,底层写死了,只有 "/**" 请求路径才能判断成功,才能访问到我们项目里来,但是改了路径请求之后就相当于无法访问进项目里来了,因此会失效。
        
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值