SpringBoot学习笔记【part08】Web开发——静态资源配置原理

导读:本篇文章讲解 SpringBoot学习笔记【part08】Web开发——静态资源配置原理,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

SpringBoot 学习笔记 Part08

1. WebMvcAutoConfiguration

SpringBoot启动默认加载 xxxAutoConfiguration 类(自动配置类),WebMvcAutoConfiguration 是 SpringMVC 功能的自动配置类。

根据我们之前的学习,打开libs里的spring的自动配置类包,
在这里插入图片描述
找到 WebMvcAutoConfiguration ,
在这里插入图片描述
观察它的按需加载条件,我们可以发现这个配置类是生效的。

@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 {}

2. WebMvcAutoConfigurationAdapter

接下来我们可以观察这个配置类给容器中放了些什么组件,其中有一个静态内部类为WebMvcAutoConfigurationAdapter,也是一个配置类。

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

在研究自动配置时,一定要注意@EnableConfigurationProperties这个注解,它是用于把配置文件里的数据和配置文件类进行绑定。我们可以顺着这个线索去找到它配置了什么。

打开WebMvcProperties和ResourceProperties配置文件类的源码,我们可以发现WebMvcProperties的绑定前缀为spring.mvc、ResourceProperties的绑定前缀为spring.resources。

2.1 有参构造器

现在看静态内部类WebMvcAutoConfigurationAdapter的方法体,方法体中有一个有参构造器。在springboot中,若只有一个有参构造器,那么这个构造方法的所有参数的值都将从IoC容器中找。

public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
    this.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();
}

对上面的各种参数进行分析,可以知道它们的功能和作用:

  • ResourceProperties resourceProperties:获取和spring.resources绑定的所有的值的对象
  • WebMvcProperties mvcProperties:获取和spring.mvc绑定的所有的值的对象
  • ListableBeanFactory:相当于找到Spring的IoC容器,也就是Spring的bean工厂
  • HttpMessageConverters:找到系统中所有的HttpMessageConverters(后面会学习)
  • ResourceHandlerRegistrationCustomizer:找到资源处理器的自定义器
  • DispatcherServletPath:DispatcherServlet处理的路径
  • ServletRegistrationBean:给应用注册Servlet、Filter、Listener等的组件

继续看方法体,可以发现该配置类为容器中加入了许多组件,且都是按需加载@ConditionalOnMissingBean,以下用springmvc自动注入视图解析器的源码为例,还有许多组件如国际化消息解析器、格式化器、文件上传等。

@Bean
@ConditionalOnMissingBean
public InternalResourceViewResolver defaultViewResolver() {
    InternalResourceViewResolver resolver = new InternalResourceViewResolver();
    resolver.setPrefix(this.mvcProperties.getView().getPrefix());
    resolver.setSuffix(this.mvcProperties.getView().getSuffix());
    return resolver;
}

2.2 资源处理的默认规则

接着往下,我们可以找到一个方法 addResourceHandlers,作用是添加资源处理器。所有的资源默认的规则都可以在这里观察到。

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});
            }
        });
    }
}

在 this.resourceProperties.isAddMappings( )我们可以发现,在spring.resources配置文件下,有一个boolean属性addMappings。

addMappings属性:默认是 true,若配置为 false 则 else 里注册一系列组件(观察代码可以发现都是一些静态资源访问的相关配置)的代码段将不会生效。

spring:
  web:
    resources:
      add-mappings: false # 修改为false,最终效果是禁用掉项目中的所有静态资源,无论怎么样都访问不了了

在addResourceHandlers里有addResourceHandler方法,观察可发现这个方法它其实就是用来注册访问规则的。

private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, Consumer<ResourceHandlerRegistration> customizer) {
   if (!registry.hasMappingForPattern(pattern)) {
        ResourceHandlerRegistration registration = registry.addResourceHandler(new String[]{pattern});
        customizer.accept(registration);
       //这个方法还有缓存控制的功能,用于将这些资源缓存一段时间。也就是我们访问过一次后,浏览器会为我们缓存,就不用每次都向服务器请求这个静态文件了。            registration.setCachePeriod(this.getSeconds(this.resourceProperties.getCache().getPeriod()));
        registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
        registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
        this.customizeResourceHandlerRegistration(registration);
    }
}

addResourceHandlers一共调用了两次,一次用于webjar规则、一次用于静态资源访问规则:

  1. 这就解释了为什么之前学习到webjar时,webjar只需要 /webjars/** 就能访问,因为再注册访问规则之后,webjar的静态资源路径就被这个方法设置成了 classpath:/META-INF/resources/webjars/

  2. 静态资源路径也调用了这个方法,同理。getStaticPathPattern和getStaticLocations方法最终获取到的值如下:

    private String staticPathPattern = "/**";
    private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{
        "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"
    };
    

    这就解释了为什么我们之前学习时会有4个默认的静态资源路径。

    当我们修改静态资源路径时,则就会按照我们修改过的静态资源路径来。在 “/**”的请求时,就会来到设置的静态资源路径拿资源,且也有缓存策略(静态资源都有缓存策略)。

此外,addResourceHandler方法还有缓存控制的功能,用于将这些资源缓存一段时间。也就是我们访问过一次后,浏览器会为我们缓存,就不用每次都向服务器请求这个静态文件了。我们也可以在配置文件中设置缓存策略的相关信息:

spring:
  web:
    resources:
      cache:
        period: 10000

3. 欢迎页的处理规则

回到一开始最外层的WebMvcAutoConfiguration自动配置类,我们可以发现里面还有一个配置类EnableWebMvcConfiguration,它也给IoC容器中也放了好多组件。其中就有我们要分析的欢迎页。

xxxHandlerMapping是springmvc的核心组件,用于处理器映射,它里面保存了每一Handler能处理哪些请求。即拿到请求后,handlermapping负责查找这个请求谁处理、那个请求谁处理,找到之后就利用反射调用能处理这个请求的那个方法。

welcomePageHandlerMapping相当于是 “欢迎页谁能处理“ 的请求映射规则,以下开始分析它的源码。

@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;
}

welcomePageHandlerMapping 有许多形参,这些参数spring都会自动从IoC容器中拿。

接着我们进入 new WelcomePageHandlerMapping()的构造函数底层,源码如下。

final class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping {

    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)) {
            //若静态资源路径不是/**,则提交一个名为index的视图交给Controller处理
            logger.info("Adding welcome page template: index");
            this.setRootViewName("index");
        }
    }
}

我们终于发现,spring底层写死了 “/“.equals(staticPathPattern) ,这也解释了为什么之前我们一修改静态资源路径,欢迎页就失效了,因为在spring底层源码中,这是完全写死了的,非 “/” 不可。

总结:由于spring底层写死的缘故,当没有修改静态资源路径时,欢迎页就为index.html。但是我们实际开发中都会修改静态资源路径以区分Controller的请求,在我们修改之后,欢迎页功能将会改为提交一个路径名为 index 的视图交给Controller处理,我们可以编写这个名为index的Controller实现一下业务和跳转。

4. favicon的处理规则

了解springmvc底层源码后,我们发现favicon跟代码没有任何关系,因为这是浏览器做的事情。浏览器会默认去找当前项目下的 /favicon.ico 并实现该功能。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/93563.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!