Spring MVC 静态资源处理

优雅 REST 风格的资源 URL 不但愿带 .html 或 .do 等后缀。javascript

因为早期的 Spring MVC 不能很好地处理静态资源,因此在 web.xml 中配置 DispatcherServlet 的请求映射时,每每采用 *.do、*.xhtml 等方式。这就决定了请求 URL 必须是一个带后缀的 URL,而没法采用真正 REST 风格的 URL 。css

若是将 DispatcherServlet 请求映射配置为 “/”,则 Spring MVC 将捕获 Web容器全部的请求,包括静态资源的请求,Spring MVC 会将它们当成一个普通请求处理,因找不到对应的处理器而致使错误。html

如何让 Spring 框架可以捕获全部 URL 的请求,同时又将静态资源的请求转由 Web 容器处理,是可将 DispatcherServlet 的请求映射配置为 “/” 的前提。因为 REST 是 Spring 的重要功能之一,因此 Spring 团队很看重静态资源处理这项任务,给出了堪称经典的两种解决方案。java

在学习这两个方案以前,先调整 web.xml 中 DispatcherServlet 的配置,使其能够捕获全部的请求。web

<servlet>
  <servlet-name>smart</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
  <servlet-name>smart</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>

经过 <url-pattern>/</url-pattern> 的配置,全部 URL 请求都将被 Spring MVC 的 DispatcherServlet 截获。spring

 

1.采用 <mvc:default-servlet-handler/>数据库

在 smart-servlet.xml 中配置 <mvc:default-servlet-handler/> 后,会在 Spring MVC 上下文中定义一个 org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler,它将充当一个检查员的角色,对进入 DispatcherServlet 的 URL 进行筛查。若是发现是静态资源的请求,就将该请求转由 Web 应用服务器默认的 Servlet 处理;若是不是静态资源的请求,则由 DispatcherServlet 继续处理。浏览器

通常 Web 应用服务器(包括 Tomcat、Jetty、Glassfish、JBoss、Resin、WebLogic 和 WebSphere)默认的 Servlet 名称都是 default,所以,DefaultServletHttpRequestHandler 能够找到它。若是用户所使用的 Web 应用服务器的默认 Servlet 名称不是 default,则须要经过 default-servlet-name 属性显式指定。缓存

<mvc:default-servlet-handler default—serv1et—name="yourServerDefaultServlet Name"/>

 

2.采用 <mvc:resources/>安全

<mvc:default-servlet-handler/> 将静态资源的处理经由 Spring MVC 框架交回 Web 应用服务器。而 <mvc:resources/> 更进一步,由 Spring MVC 框架本身处理静态资源,并添加一些有用的附加功能。

首先,<mvc:resources/> 容许静态资源放置在任何地方,如 WEB-INF 目录下、类路径下等,甚至能够将 JavaScript 等静态文件打包到 JAR 包中。经过 location 属性指定静态资源的位置,因为 location 属性是 Resource 类型,所以可使用诸如 "classpath:" 等的资源前缀指定资源位置。传统 Web 容器的静态资源只能放在 Web 容器的根路径下,<mvc:resources/> 则彻底打破了这个限制。

其次,<mvc:resources/> 依据当前著名的 Page Speed、YSlow 等浏览器优化原则对静态资源提供优化。能够经过 cacheSeconds 属性指定静态资源在浏览器端的缓存时间,通常可将该时间设置为一年,以充分利用浏览器端的缓存。在输出静态资源时,会根据配置设置好响应报文头的 Expires 和 Cache-Control 值。

在接收到静态资源的获取请求时,会检查请求头的 Last-Modified 值。若是静态资源没有发生变化,则直接返回 303 响应状态码,指示客户端使用浏览器缓存的数据,而非将静态资源的内容输出到客户端,以充分节省带宽,提升程序性能。

在 smart-servlet.xml 中添加如下配置:

<mvc:resources mapping="/resources/**" location="/,classpath:/META—INF/publicResources/" />

以上配置将 Web 根路径 “/” 及类路径 /META-INF/publicResources/ 下的目录映射为 /resources 路径。假设 Web 根路径下拥有 images 和 js 这两个资源目录,则能够经过以下图所示的方式引用静态资源。

假设类路径 /META-INF/publicResources/ 下还拥有 images/bg1.gif 和 js/test1.js,则也能够在网页中经过 /resources/images/bg1.gif 和 /resources/js/test1.js 进行引用,以下面代码所示。

<script src="<c:url value="/resources/js/test.js"/>" type="text/javascript"></script>

因为 <mvc:resources/> 能够将多个物理路径映射为一个逻辑路径,所以,一个用逻辑路径表示的资源在多个物理路径下都存在。对于这个问题,<mvc:resources/> 的处理机制是,只要在一个物理路径下找到匹配的资源后就返回,查找的顺序和物理路径在 location 中的配置顺序一致。

聪明的读者可能会问:既然将 Web 根路径 “/” 映射为 “/resources/**”,是否能够在网页中经过 "/resources/WEB-INF/web.xml” 访问这个敏感的文件呢?答案是否认的。Spring MVC 在处理映射的静态资源时,会查看引用路径是否包含 WEB-INF 或 META-INF。若是包括,则直接返回 null 值,以保护安全文件不泄露出去。固然,若是将 /WEB-INF/ 设置在 location 属性中,则能够经过 /resources/web.xml 的 URL 查看到 web.xml。

<mvc:resources mapping="/resources/**" location="/WEB-INF/"/>

因此使用 <mvc:resources/> 时须要特别注意,不要一不当心将不指望暴露的资源泄露出去。

经过 <mvc:resources/> 的 cache-period 属性能够设置静态资源在客户端浏览器中的缓存有效时间。

<mvc:resources mapping="/resources/**" location="/,classpath:/META—INF/publicResources/"  cache-period="31536000"/>

 通常状况下,将 cache-period 设置为一年,以便充分利用客户端的缓存数据。

在发布新版本的应用时,即便服务器端的 JavaScript、css 等静态资源文件已经发生了变化,可是因为客户端浏览器自己缓存管理机制的问题,客户端并不会从服务器端下载新的静态资源。一个好的解决办法是:网页中引用静态资源的路径添加应用的发布版本号,这样在发布新的部署版本时,因为版本号的变动形成网页中静态资源路径发生更改,从而使这些静态资源成为“新的资源”,客户端浏览器就会下载这个“新的资源”,而不会使用缓存中的数据。针对这个解决思路,能够经过 <mvc:resources/> 的静态资源逻辑路径给出一个通用的解决方案。

将发布版本号包含到 <mvc:resources/> 的静态资源逻辑路径中。首先建立一个 ServletContextAware 实现类,以下面代码所示。

import javax.servlet.ServletContext;

import org.springframework.web.context.ServletContextAware;

public class ResourcePathExposer implements ServletContextAware {
    private ServletContext servletContext;
    private String resourceRoot;

    public void init() {
        String version = "1.2.1";//①在实际应用中,能够在外部属性文件或数据库中保存应用的发布版本号,在此处获取之。此处仅仅提供一个模拟值。
        resourceRoot = "/resources-" + version;//②资源逻辑路径带上应用的发布版本号
        getServletContext().setAttribute("resourceRoot", 
                getServletContext().getContextPath()+resourceRoot);//③在资源逻辑路径暴露到ServletContext的属性列表中
    }

    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    public String getResourceRoot() {
        return resourceRoot;
    }

    public ServletContext getServletContext() {
        return servletContext;
    }    
}

在 ResourcePathExposer 中获取应用程序的发布版本号,产生一个带版本号的静态资源路径 resourceRoot,同时将其值发布到 ServletContext 中,这样 JSP 文件就能够经过 ${resourceRoot} 引用其值了。

接下来要调整中的配置,以便使用带版本的静态资源逻辑路径。

<bean id="rpe" class="com.smart.web.ResourcePathExposer" init-method="init"/>
<mvc:resources mapping="#{rpe.resourceRoot}/**" location="/" cache-period="31536000"/>

在①处配置好 ResourcePathExposer,并指定其初始化方法为 init(),以便在容器启动时让其初始化 resourceRoot 的值。因为其实现了 ServletContextAware 接口,所以,Spring 会在初始化该 Bean 时将 ServletContext 引用注入进来。

在②处经过 Spring EL 表达式引用 ResourcePathExposer 的 resourceRoot 属性值,生成动态的静态资源逻辑路径。

最后调整网页中引用静态资源的方式,以下面代码所示。

<script src="<c:url value="${resourceRoot}/js/test.js"/>" type="text/javascript"></script>

因为引用的 resourceRoot 值和 <mvc:resources/> 经过 #{rpe.resourceRoot} 引用的值是同样的,因此能够正确访问到物理静态资源。这样,在每次发布新版本后,随着发布版本号的更改,客户端就会自动下载新的静态资源。

相关文章
相关标签/搜索