Spring Boot 静态资源处理

目录(?)[-]javascript

  1. 默认资源映射
  2. 自定义资源映射
    1. 自定义目录
    2. 使用外部目录
    3. 经过配置文件配置
  3. 页面中使用
  4. 使用webjars
    1. 简单应用
    2. 版本号统一管理
  5. 静态资源版本管理
  6. 总结

Spring Boot 默认为咱们提供了静态资源处理,使用 WebMvcAutoConfiguration 中的配置各类属性。css

建议你们使用Spring Boot的默认配置方式,若是须要特殊处理的再经过配置进行修改。前端

若是想要本身彻底控制WebMVC,就须要在@Configuration注解的配置类上增长@EnableWebMvc(@SpringBootApplication 注解的程序入口类已经包含@Configuration),增长该注解之后WebMvcAutoConfiguration中配置就不会生效,你须要本身来配置须要的每一项。这种状况下的配置仍是要多看一下WebMvcAutoConfiguration类。java

咱们既然是快速使用Spring Boot,并不想过多的本身再从新配置。本文仍是主要针对Spring Boot的默认处理方式,部分配置在application 配置文件中(.properties 或 .yml)jquery

默认资源映射

咱们在启动应用的时候,能够在控制台中看到以下信息:web

2016-01-08 09:29:30.362  INFO 24932 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-01-08 09:29:30.362  INFO 24932 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-01-08 09:29:30.437  INFO 24932 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]

其中默认配置的 /** 映射到 /static (或/public、/resources、/META-INF/resources) 
其中默认配置的 /webjars/** 映射到 classpath:/META-INF/resources/webjars/ 
PS:上面的 static、public、resources 等目录都在 classpath: 下面(如 src/main/resources/static)。spring

若是我按以下结构存放相同名称的图片,那么Spring Boot 读取图片的优先级是怎样的呢? 
以下图: 
这里写图片描述浏览器

当咱们访问地址 http://localhost:8080/fengjing.jpg 的时候,显示哪张图片?这里博主能够直接告诉你们,优先级顺序为:META/resources > resources > static > public 
若是咱们想访问pic2.jpg,请求地址 http://localhost:8080/img/pic2.jpg缓存

自定义资源映射

上面咱们介绍了Spring Boot 的默认资源映射,通常够用了,那咱们如何自定义目录? 
这些资源都是打包在jar包中的,而后实际应用中,咱们还有不少资源是在管理系统中动态维护的,并不可能在程序包中,对于这种随意指定目录的资源,如何访问?springboot

自定义目录

以增长 /myres/* 映射到 classpath:/myres/* 为例的代码处理为: 
实现类继承 WebMvcConfigurerAdapter 并重写方法 addResourceHandlers (对于 WebMvcConfigurerAdapter 上篇介绍拦截器的文章中已经有提到)

package org.springboot.sample.config;

import org.springboot.sample.interceptor.MyInterceptor1;
import org.springboot.sample.interceptor.MyInterceptor2;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class MyWebAppConfigurer 
        extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/myres/**").addResourceLocations("classpath:/myres/");
        super.addResourceHandlers(registry);
    }

}

 访问myres 文件夹中的fengjing.jpg 图片的地址为 http://localhost:8080/myres/fengjing.jpg 
这样使用代码的方式自定义目录映射,并不影响Spring Boot的默认映射,能够同时使用。

若是咱们将/myres/* 修改成 /* 与默认的相同时,则会覆盖系统的配置,能够屡次使用 addResourceLocations 添加目录,优先级先添加的高于后添加的。

// 访问myres根目录下的fengjing.jpg 的URL为 http://localhost:8080/fengjing.jpg (/** 会覆盖系统默认的配置)
// registry.addResourceHandler("/**").addResourceLocations("classpath:/myres/").addResourceLocations("classpath:/static/");

 其中 addResourceLocations 的参数是动参,能够这样写 addResourceLocations(“classpath:/img1/”, “classpath:/img2/”, “classpath:/img3/”);

使用外部目录

若是咱们要指定一个绝对路径的文件夹(如 H:/myimgs/ ),则只须要使用 addResourceLocations 指定便可。

// 能够直接使用addResourceLocations 指定磁盘绝对路径,一样能够配置多个位置,注意路径写法须要加上file:
registry.addResourceHandler("/myimgs/**").addResourceLocations("file:H:/myimgs/");

 经过配置文件配置

上面是使用代码来定义静态资源的映射,其实Spring Boot也为咱们提供了能够直接在 application.properties(或.yml)中配置的方法。 
配置方法以下:

# 默认值为 /**
spring.mvc.static-path-pattern=
# 默认值为 classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/ 
spring.resources.static-locations=这里设置要指向的路径,多个使用英文逗号隔开,

使用 spring.mvc.static-path-pattern 能够从新定义pattern,如修改成 /myres/** ,则访问static 等目录下的fengjing.jpg文件应该为 http://localhost:8080/myres/fengjing.jpg ,修改以前为 http://localhost:8080/fengjing.jpg 
使用 spring.resources.static-locations 能够从新定义 pattern 所指向的路径,支持 classpath: 和 file: (上面已经作过说明) 
注意 spring.mvc.static-path-pattern 只能够定义一个,目前不支持多个逗号分割的方式。

页面中使用

上面几个例子中也已经说明了怎么访问静态资源,其实在页面中使用不论是jsp仍是freemarker,并无什么特殊之处,也咱们平时开发web项目同样便可。 
下面是个人index.jsp:

<body>
    <img alt="读取默认配置中的图片" src="${pageContext.request.contextPath }/pic.jpg">
    <br/>
    <img alt="读取自定义配置myres中的图片" src="${pageContext.request.contextPath }/myres/fengjing.jpg">
</body>

使用webjars

先说一下什么是webjars?咱们在Web开发中,前端页面中用了愈来愈多的JS或CSS,如jQuery等等,平时咱们是将这些Web资源拷贝到Java的目录下,这种经过人工方式拷贝可能会产生版本偏差,拷贝版本错误,前端页面就没法正确展现。 
WebJars 就是为了解决这种问题衍生的,将这些Web前端资源打包成Java的Jar包,而后借助Maven这些依赖库的管理,保证这些Web资源版本惟一性。

WebJars 就是将js, css 等资源文件放到 classpath:/META-INF/resources/webjars/ 中,而后打包成jar 发布到maven仓库中。

简单应用

以jQuery为例,文件存放结构为:

META-INF/resources/webjars/jquery/2.1.4/jquery.js
META-INF/resources/webjars/jquery/2.1.4/jquery.min.js
META-INF/resources/webjars/jquery/2.1.4/jquery.min.map
META-INF/resources/webjars/jquery/2.1.4/webjars-requirejs.js

 Spring Boot 默认将 /webjars/** 映射到 classpath:/META-INF/resources/webjars/ ,结合咱们上面讲到的访问资源的规则,即可以得知咱们在JSP页面中引入jquery.js的方法为:

<script type="text/javascript" src="${pageContext.request.contextPath }/webjars/jquery/2.1.4/jquery.js"></script>

想实现这样,咱们只须要在pom.xml 文件中添加jquery的webjars 依赖便可,以下:

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>2.1.4</version>
</dependency>

 版本号统一管理

可是咱们实际开发中,可能会遇到升级版本号的状况,若是咱们有100多个页面,几乎每一个页面上都有按上面引入jquery.js 那么咱们要把版本号更换为3.0.0,一个一个替换显然不是最好的办法。 
如何来解决?按以下方法处理便可。

首先在pom.xml 中添加依赖:

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>webjars-locator</artifactId>
</dependency>

 而后增长一个WebJarsController:

package org.springboot.sample.controller;

import javax.servlet.http.HttpServletRequest;

import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.HandlerMapping;
import org.webjars.WebJarAssetLocator;

/**
 * 处理WebJars,自动读取版本号
 *
 * @author   单红宇(365384722)
 * @myblog  http://blog.csdn.net/catoop/
 * @create    2016年1月8日
 */
@Controller
public class WebJarsController {

     private final WebJarAssetLocator assetLocator = new WebJarAssetLocator();

    @ResponseBody
    @RequestMapping("/webjarslocator/{webjar}/**")
    public ResponseEntity<Object> locateWebjarAsset(@PathVariable String webjar, HttpServletRequest request) {
        try {
            String mvcPrefix = "/webjarslocator/" + webjar + "/"; // This prefix must match the mapping path!
            String mvcPath = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
            String fullPath = assetLocator.getFullPath(webjar, mvcPath.substring(mvcPrefix.length()));
            return new ResponseEntity<>(new ClassPathResource(fullPath), HttpStatus.OK);
        } catch (Exception e) {
            return new ResponseEntity<>(HttpStatus.NOT_FOUND);
        }
    }
}

 最后在页面中使用的方式:

<script type="text/javascript" src="${pageContext.request.contextPath }/webjarslocator/jquery/jquery.js"></script>

静态资源版本管理

Spring 默认提供了静态资源版本映射的支持。 
当咱们的资源内容发生改变时,因为浏览器缓存,用户本地的资源仍是旧资源,为了防止这种状况发生致使的问题。咱们可能会选择在资源文件后面加上参数“版本号”或其余方式。

使用版本号参数,如:

<script type="text/javascript" src="${pageContext.request.contextPath }/js/common.js?v=1.0.1"></script>

用这种方式,当咱们文件修改后,手工修改版本号来达到URL文件不被浏览器缓存的目的。一样也存在不少文件都须要修改的问题。或者有的人会增长时间戳的方式,这样我认为是最不可取的,每次浏览器都要请求为服务器增长了没必要要的压力。

然而Spring在解决这种问题方面,提供了2种解决方式。 
* 资源名称md5方式 * 
1. 修改 application.properties 配置文件(或.yml)

spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**

全部 /** 请求的静态资源都会被处理。

  1. 建立 ResourceUrlProviderController 文件
package org.springboot.sample.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.servlet.resource.ResourceUrlProvider;

/**
 * 处理静态资源URL
 *
 * @author 单红宇(365384722)
 * @myblog http://blog.csdn.net/catoop/
 * @create 2016年1月8日
 */
@ControllerAdvice
public class ResourceUrlProviderController {

    @Autowired
    private ResourceUrlProvider resourceUrlProvider;

    @ModelAttribute("urls")
    public ResourceUrlProvider urls() {
        return this.resourceUrlProvider;
    }
}

 在页面中使用的写法

<script type="text/javascript" src="${pageContext.request.contextPath }${urls.getForLookupPath('/js/common.js') }"></script>

 当咱们访问页面后,HTML中实际生成的代码为:

<script type="text/javascript" src="/myspringboot/js/common-c6b7da8fffc9be141b48c073e39c7340.js"></script>
  • 其中 /myspringboot 为我这个项目的 contextPath

* 资源版本号方式 * 
该方式本人以为并没有多大意义,也不作详细说明,这是对全部资源的统一版本控制,不像上面一个md5是针对文件的。 
除了在 application.properties(或.yml)中的配置有所区别,页面使用和md5的同样。

spring.resources.chain.strategy.fixed.enabled=true
spring.resources.chain.strategy.fixed.paths=/js/**,/v1.0.0/**
spring.resources.chain.strategy.fixed.version=v1.0.0

 这样配置后,以上面 common.js 为例,实际页面中生成的HTML代码为:

<script type="text/javascript" src="/myspringboot/v1.0.0/js/common.js"></script>

* md5与版本号方式的处理原理 * 
页面中首先会调用urls.getForLookupPath方法,返回一个/v1.0.0/js/common.js或/css/common-c6b7da8fffc9be141b48c073e39c7340.js 
而后浏览器发起请求。 
当请求的地址为md5方式时,会尝试url中的文件名中是否包含-,若是包含会去掉后面这部分,而后去映射的目录(如/static/)查找/js/common.js文件,若是能找到就返回。

当请求的地址为版本号方式时,会在url中判断是否存在/v1.0.0 ,若是存在,则先从URL中把 /v1.0.0 去掉,而后再去映射目录查找对应文件,找到就返回。

总结

有这么多方式来管理咱们的资源文件,然而在实际应用中虽然也都有可能用到(存在就有存在的道理嘛),可是凭借我的经验来讲。 
1. 咱们使用第三方的库时,建议使用webjars的方式,经过动态版本号(webjars-locator 的方式)来使用(由于第三方库在项目开发中变更频率很小,即使是变更也是版本号的修改)。 
2. 咱们使用本身存放在静态资源映射目录中的资源的时候,建议使用md5 资源文件名的方式来使用(项目开发中一些css、js文件会常常修改)。 
3. 项目素材文件建议放到 classpath:/static (或其余)目录中,打包在项目中,经过CMS维护的一些图片和资源,咱们使用配置引用到具体的磁盘绝对路径来使用。 
4. 注意使用md5文件名方式的时候,Spring 是有缓存机制的,也就是说,在服务不重启的状况下,你去变更修改这些资源文件,其文件名的md5值并不会改变,只有重启服务再次访问才会生效。若是须要每次都获取实际文件的md5值,须要重写相关类来实现,咱们不建议这样作,由于一直去计算文件md5值是须要性能代价的。深刻 Spring 系列之静态资源处理

    上一篇Spring Boot 拦截器

   下一篇Spring Boot 启动加载数据 CommandLineRunner

 

  相关文章推荐

相关文章
相关标签/搜索