zoukankan      html  css  js  c++  java
  • 从SpringBoot源码看资源映射原理

     

    前言

    很多的小伙伴刚刚接触SpringBoot的时候,可能会遇到加载不到静态资源的情况。

    比如html没有样式,图片无法加载等等。

    今天王子就与大家一起看看SpringBoot中关于资源映射部分的主要源码实现。

    建议环境允许的情况下,小伙伴们自己使用idea创建一个springBoot项目,跟着文章和王子一起看一看源码,更容易理解。

    SSM中的资源映射

    在谈SpringBoot之前,我们先回顾一下SSM中关于资源配置是如何实现的。

    在SSM环境下,一般我们可以通过<mvc:resources />节点来配置不拦截静态资源,就像下边这样:

    <mvc:resources mapping="/js/**" location="/js/" />
    <mvc:resources mapping="/css/**" location="/css/" />
    <mvc:resources mapping="/img/**" location="/img/" />

    这里的/**表示的是可以匹配任意层级的路径,属于ant风格表达式,感兴趣的小伙伴自行百度了解即可,所以也可以写成下面这样

    <mvc:resources mapping="/**" location="/" />

    上边的这种配置方式是属于XML配置的方式,SpringMVC的配置方式除了XML配置,也是可以通过java代码配置的,只需要我们自己定义一个类,来继WebMvcConfigurationSupport这个类即可,我们看一下WebMvcConfigurationSupport的源码部分:

        /**
         * Override this method to add resource handlers for serving static resources.
         * @see ResourceHandlerRegistry
         */
        protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        }

    上边的注释写的很清楚,重写这个方法来增加一个静态资源的映射。那么具体怎么写呢?

    我们再按照注释看一下ResourceHandlerRegistry的源码,重点看一下对这个类的注释,如下。

     /* <p>To create a resource handler, use {@link #addResourceHandler(String...)} providing the URL path patterns
     / * for which the handler should be invoked to serve static resources (e.g. {@code "/resources/**"}).

    大概意思就是为了创建资源的处理器,要调用addResourceHandler方法来提供url的表达式,这个方法是为了服务静态资源的(ps:王子的英语水平也一般,了解大意即可)

    然后我们去看addResourceHandler方法:

        /**
         * Add a resource handler for serving static resources based on the specified URL path patterns.
         * The handler will be invoked for every incoming request that matches to one of the specified
         * path patterns.
         * <p>Patterns like {@code "/static/**"} or {@code "/css/{filename:\w+\.css}"} are allowed.
         * See {@link org.springframework.util.AntPathMatcher} for more details on the syntax.
         * @return a {@link ResourceHandlerRegistration} to use to further configure the
         * registered resource handler
         */
        public ResourceHandlerRegistration addResourceHandler(String... pathPatterns) {
            ResourceHandlerRegistration registration = new ResourceHandlerRegistration(pathPatterns);
            this.registrations.add(registration);
            return registration;
        }

    我们看到这个方法执行后返回了一个新的类ResourceHandlerRegistration

    那我们再来看一下这个类中的核心方法

        /**
         * Add one or more resource locations from which to serve static content.
         * Each location must point to a valid directory. Multiple locations may
         * be specified as a comma-separated list, and the locations will be checked
         * for a given resource in the order specified.
         * <p>For example, {{@code "/"}, {@code "classpath:/META-INF/public-web-resources/"}}
         * allows resources to be served both from the web application root and
         * from any JAR on the classpath that contains a
         * {@code /META-INF/public-web-resources/} directory, with resources in the
         * web application root taking precedence.
         * <p>For {@link org.springframework.core.io.UrlResource URL-based resources}
         * (e.g. files, HTTP URLs, etc) this method supports a special prefix to
         * indicate the charset associated with the URL so that relative paths
         * appended to it can be encoded correctly, e.g.
         * {@code [charset=Windows-31J]https://example.org/path}.
         * @return the same {@link ResourceHandlerRegistration} instance, for
         * chained method invocation
         */
        public ResourceHandlerRegistration addResourceLocations(String... resourceLocations) {
            this.locationValues.addAll(Arrays.asList(resourceLocations));
            return this;
        }

    上边注释的大意就是增加一个或者多个静态资源路径,并举了一些例子。源码我们就看到这里。

    所以我们可以像这样实现资源的映射:

    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
    
    /**
     * @author liumeng
     * @Date: 2020/9/25 09:20
     * @Description:
     */
    @Configuration
    public class SpringMvcConfig extends WebMvcConfigurationSupport {
        @Override
        protected void addResourceHandlers(ResourceHandlerRegistry registry) {
            registry.addResourceHandler("/**").addResourceLocations("/");
        }
    }

    关于SpringMVC中的资源映射部分就介绍到这,那么我们继续来看SpringBoot的资源映射吧。

    SpringBoot的资源映射

    其实SpringBoot的资源映射也是一脉相承的,当我们初始化一个SpringBoot项目后,静态资源会默认存在resource/static目录中,那么SpringBoot的底层是怎么实现的呢,接下来我们就去源码里探索一下。

    SpringBoot的源码在WebMvcAutoConfiguration这个类中,我们发现了熟悉的代码:

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

    这里面我们重点看下边这部分

           String staticPathPattern = this.mvcProperties.getStaticPathPattern();
                if (!registry.hasMappingForPattern(staticPathPattern)) {
                    customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
                            .addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))
                            .setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
                }

    我们看一下getStaticPathPattern()方法的实现,发现获得的就是

        /**
         * Path pattern used for static resources.
         */
        private String staticPathPattern = "/**";

    这个属性的值,默认是/**。

    然后我们再看this.resourceProperties.getStaticLocations()方法,发现获得的是

        private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;

    而CLASSPATH_RESOURCE_LOCATIONS是一个常量,值如下:

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

    在这里我们看到了四个值,static就是其中一个,到这里我们就明白了SpringBoot的静态资源为什么会存在resource/static这个目录下,而且放在以上4个目录中是都可以读取到的。

    实际上SpringBoot默认的静态资源是5个,我们再来看getResourceLocations方法,如下:

        static String[] getResourceLocations(String[] staticLocations) {
            String[] locations = new String[staticLocations.length + SERVLET_LOCATIONS.length];
            System.arraycopy(staticLocations, 0, locations, 0, staticLocations.length);
            System.arraycopy(SERVLET_LOCATIONS, 0, locations, staticLocations.length, SERVLET_LOCATIONS.length);
            return locations;
        }

    可以看到,除了我们之前看到的4个路径,这个方法里还新增了一个SERVLET_LOCATIONS的路径,点进去看一下

        private static final String[] SERVLET_LOCATIONS = { "/" };

    发现就是一个"/"。

    所以实际上SpringBoot的默认静态资源路径有5个:

    "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/",“/”

    自定义配置

    好了,到现在我们已经知道了SpringBoot的默认资源映射来源,那么我们如何配置自定义的资源映射路径呢?

    其实我们可以直接通过application.properties配置,如下:

    spring.resources.static-locations=classpath:/
    spring.mvc.static-path-pattern=/**

    那么为什么这样配置就可以了呢,是因为相应的类上使用了下面的注解:

    @ConfigurationProperties(prefix = "spring.mvc")

    @ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)

    本文就不对这个注解再做深入研究了。感兴趣的小伙伴可以持续关注我们的后续文章。

    除了通过配置文件自定义,还可以通过java代码进行配置,这种方式和我们上文说到的SpringMvc方式比较类似,只不过我们这次是实现的WebMvcConfigurer这个接口

    实现方式如下:

    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    @Configuration
    public class ResourcesConfig implements WebMvcConfigurer
    {
    
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry)
        {
            registry.addResourceHandler( "/**").addResourceLocations("classpath:/aaa");
        }
    }

    有了上边文章的铺垫,相信大家对于这段代码应该秒懂了吧。

    到这里,小伙伴们是否会有个疑问,WebMvcConfigurer和WebMvcConfigurationSupport有什么关系呢?

    这个问题本文就不再探索了,留给大家自行探索。

    总结

    好了,今天王子和大家一起从SpringMVC的源码开始探索,引出了SpringBoot的资源映射配置原理。

    又介绍了SpringBoot自定义资源映射路径的两种方式,相信小伙伴们会有一个比较深刻的印象了。

    本文到这里就结束了,如果觉得内容对你有所帮助,那么欢迎持续关注后续文章。

    往期文章推荐:

    Windows下使用Nginx+Tomcat做负载均衡

    聊聊分布式下的WebSocket解决方案

  • 相关阅读:
    博客作业06--图
    博客作业05--查找
    博客作业04--树
    博客作业03--栈和队列
    博客作业2---线性表
    博客作业01-抽象数据类型
    C语言最后一次博客作业
    C语言第十次博客作业--结构体
    C语言第九次博客作业--指针
    C语言第八次博客作业--字符数组
  • 原文地址:https://www.cnblogs.com/lm970585581/p/13725352.html
Copyright © 2011-2022 走看看