zoukankan      html  css  js  c++  java
  • Spring MVC 静态资源处理

    优雅 REST 风格的资源 URL 不希望带 .html 或 .do 等后缀。

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

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

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

    在学习这两个方案之前,先调整 web.xml 中 DispatcherServlet 的配置,使其可以捕获所有的请求。

    <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 截获。

    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} 引用的值是一样的,所以可以正确访问到物理静态资源。这样,在每次发布新版本后,随着发布版本号的更改,客户端就会自动下载新的静态资源。

  • 相关阅读:
    10. Regular Expression Matching
    9. Palindrome Number
    6. ZigZag Conversion
    5. Longest Palindromic Substring
    4. Median of Two Sorted Arrays
    3. Longest Substring Without Repeating Characters
    2. Add Two Numbers
    链式表的按序号查找
    可持久化线段树——区间更新hdu4348
    主席树——树链上第k大spoj COT
  • 原文地址:https://www.cnblogs.com/jwen1994/p/11210845.html
Copyright © 2011-2022 走看看