spring mvc處理靜態資源


servlet的url映射定義為'/'表示映射全部路徑

struts的過濾器是*.action,在spring mvc中設置成*.action或者*.do......也是可以的,但是spring mvc就是霸氣,它就是要處理一切路徑.

對於靜態資源,在tomcat服務器上,必須通過servlet才能訪問,除此之外別無他法.

即便是你自己不寫servlet來訪問靜態資源,tomcat無論如何也要找到一個servlet來處理靜態資源,所以別覺得用servlet處理會慢,無論如何都要用servlet來處理.

一言以蔽之,在tomcat容器中,處理資源的基本單位是servlet,進行數據操作計算的基本單位也是servlet.

前人之述備矣,天下文章一大抄.

   Spring MVC 中的核心 servlet - DispatcherServlet,我們在 web.xml 文件中通常這樣定義:

<servlet>  
  <servlet-name>mvc</servlet-name>  
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  <init-param>  
    <param-name>contextConfigLocation</param-name>  
    <param-value>/WEB-INF/classes/conf/spring/mvc-*.xml</param-value>  
  </init-param>  
  <load-on-startup>1</load-on-startup>  
</servlet>  
  
<servlet-mapping>  
  <servlet-name>mvc</servlet-name>  
  <url-pattern>/</url-pattern>  
</servlet-mapping>  

        很明顯,該 servlet 對應的 url-pattern 定義成 /,因此該 servlet 會匹配上諸如 /images/a.jpg, /css/hello.css 等這些靜態資源,甚至包括 /jsp/stock/index.jsp 這些 jsp 也會匹配。但是並沒有定義相應的 Controller 來處理這些資源,因此這些請求通常是無法完成的。
        很麻煩?沒有 Struts 方便?此言差矣,因此你在 Struts 定義其核心 filter 的 url-pattern 是 *.action,當然不會對處理 jsp 和靜態資源操作影響了。Spring MVC 若也定義類似的 url-pattern,同樣不存在問題。
        說到這里,我們應該想一個問題。Tomcat 中,只有 servlet 能夠處理請求,即使是 jsp,也會被編譯成 servlet。我們即便使用 Struts,定義 *.action 的url-pattern,那 .css, *.gfi 等這些靜態資源到底是誰來處理了???你可不要想當然的認為我不是輸入了圖片的路徑了嗎?如,/images/a/b/c.gif。請注意,servlet 容器中,只有 servlet 采用處理資源!
        由 servlet 處理這些資源那是一定了。不過,不同的 servlet 容器/應用服務器,處理這些靜態資源的 servlet 的名字不大一樣:

  • Tomcat, Jetty, JBoss, and GlassFish:默認 Servlet 名字為 "default"
  • Google App Engine:默認 Servlet 名字為 "_ah_default"
  • Resin:默認 Servlet 名字為 "resin-file"
  • WebLogic:默認 Servlet 名字為 "FileServlet"
  • WebSphere:默認 Servlet 名字為 "SimpleFileServlet"

    ◇ 方案一:激活 Tomcat 的 defaultServlet 來處理靜態資源

<servlet-mapping>  
  <servlet-name>default</servlet-name>  
  <url-pattern>*.jpg</url-pattern>  
</servlet-mapping>  
<servlet-mapping>  
  <servlet-name>default</servlet-name>  
  <url-pattern>*.js</url-pattern>  
</servlet-mapping>  
<servlet-mapping>  
  <servlet-name>default</servlet-name>  
  <url-pattern>*.css</url-pattern>  
</servlet-mapping>  

        每種類型的靜態資源需要分別配置一個 servlet-mapping,同時,要寫在 DispatcherServlet 的前面, 讓 defaultServlet 先攔截。
    ◇ 方案二:Spring 3.0.4 以后版本提供了 <mvc:resources />

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

        /resources/** 映射到 ResourceHttpRequestHandler 進行處理,location 指定靜態資源的位置,可以是 web application 根目錄下、jar 包里面,這樣可以把靜態資源壓縮到 jar 包中。cache-period 可以使得靜態資源進行 web cache。
        使用 <mvc:resources /> 元素,會把 mapping 的 URI 注冊到 SimpleUrlHandlerMapping 的 urlMap 中,key 為 mapping 的 URI pattern 值,而 value 為 ResourceHttpRequestHandler,這樣就巧妙的把對靜態資源的訪問由 HandlerMapping 轉到 ResourceHttpRequestHandler 處理並返回,所以就支持 classpath 目錄, jar 包內靜態資源的訪問。
    ◇ 方案三:使用 <mvc:default-servlet-handler />
        <mvc:default-servlet-handler /> 會把 "/**" url 注冊到 SimpleUrlHandlerMapping 的 urlMap 中,把對靜態資源的訪問由 HandlerMapping 轉到 org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler 處理並返回。DefaultServletHttpRequestHandler 使用就是各個 Servlet 容器自己的默認 Servlet。
        補充說明下以上提到的 HandlerMapping 的 order 的默認值:

  • DefaultAnnotationHandlerMapping:0
  • <mvc:resources /> 自動注冊的 SimpleUrlHandlerMapping:2147483646
  • <mvc:default-servlet-handler/> 自動注冊的 SimpleUrlHandlerMapping:2147483647

        Spring 會先執行 order 值比較小的。當訪問一個 a.jpg 圖片文件時,先通過 DefaultAnnotationHandlerMapping 來找處理器,一定是找不到的,我們沒有叫 a.jpg 的 Controller。再按 order 值升序找,由於最后一個 SimpleUrlHandlerMapping 是匹配 "/**" 的,所以一定會匹配上,再響應圖片。

        Spring MVC 中,訪問一個圖片,還要走層層匹配。性能肯定好不到哪里去。不僅僅是 Spring MVC,即便 Struts,它們畢竟存活於 servlet 容器,只要由 servlet 容器處理這些靜態資源,必然要將這些資源讀入 JVM 的內存區中。所以,處理靜態資源,我們通常會在前端加 apache 或 nginx。

其中處理靜態資源的類是org.springframework.web.servlet.resource.ResourceHttpRequestHandler,而且在location的描述中說明Each location must point to a valid directory. 即每個location都必須指向一個有效的目錄。

下面看一下org.springframework.web.servlet.resource.ResourceHttpRequestHandler中是如何處理靜態資源請求的: 

首先它實現了org.springframework.web.HttpRequestHandler這個接口的handleRequest方法:

public void handleRequest(HttpServletRequest request, HttpServletResponse response)  
        throws ServletException, IOException {  
    // 這里根據配置來設置緩存的header  
    checkAndPrepare(request, response, true);  
  
    // 獲取要獲取的資源,如果不存在,直接返回404錯誤  
    Resource resource = getResource(request);  
    if (resource == null) {  
        logger.debug("No matching resource found - returning 404");  
        response.sendError(HttpServletResponse.SC_NOT_FOUND);  
        return;  
    }  
  
    // 省略部分代碼……  
    // 返回相應的資源,即把資源文件寫到響應中  
    writeContent(response, resource);  
}  

而getResource方法實現如下:

protected Resource getResource(HttpServletRequest request) {  
    String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);  
    // 省略部分處理和檢驗路徑的代碼……  
  
    // 這里循環從配置的location下查找請求的靜態資源  
    for (Resource location : this.locations) {  
        try {  
            if (logger.isDebugEnabled()) {  
                logger.debug("Trying relative path [" + path + "] against base location: " + location);  
            }  
            Resource resource = location.createRelative(path);  
            // 判斷資源存在而且能夠讀取  
            if (resource.exists() && resource.isReadable()) {  
                // 這里就是3.2.12及以后處理上的差別,在3.2.12前,不會判斷該資源是否在指定的路徑下,直接就返回了resource,而3.2.12及以后做了如下判斷  
                if (isResourceUnderLocation(resource, location)) {  
                    if (logger.isDebugEnabled()) {  
                        logger.debug("Found matching resource: " + resource);  
                    }  
                    return resource;  
                }  
                else {  
                    if (logger.isTraceEnabled()) {  
                        logger.trace("resource=\"" + resource + "\" was successfully resolved " +  
                                "but is not under the location=\"" + location);  
                    }  
                    return null;  
                }  
            }  
            else if (logger.isTraceEnabled()) {  
                logger.trace("Relative resource doesn't exist or isn't readable: " + resource);  
            }  
        }  
        catch (IOException ex) {  
            logger.debug("Failed to create relative resource - trying next resource location", ex);  
        }  
    }  
    return null;  
}  

下面看看isResourceUnderLocation的實現:

private boolean isResourceUnderLocation(Resource resource, Resource location) throws IOException {  
    if (!resource.getClass().equals(location.getClass())) {  
        return false;  
    }  
    String resourcePath;  
    String locationPath;  
    if (resource instanceof UrlResource) {  
        resourcePath = resource.getURL().toExternalForm();  
        locationPath = location.getURL().toExternalForm();  
    }  
    else if (resource instanceof ClassPathResource) {  
        resourcePath = ((ClassPathResource) resource).getPath();  
        locationPath = ((ClassPathResource) location).getPath();  
    }  
    else if (resource instanceof ServletContextResource) {  
        resourcePath = ((ServletContextResource) resource).getPath();  
        locationPath = ((ServletContextResource) location).getPath();  
    }  
    else {  
        resourcePath = resource.getURL().getPath();  
        locationPath = location.getURL().getPath();  
    }  
    // 這里是對路徑的處理,如果我們配置的是/res/**,那么直接拼接成了/res/**/,如果請求資源為/res/jquery.js,
//那么會判斷res/jquery.js是否以/res/**/開頭,如果不是,則返回該location下沒有該資源,導致不能404錯誤
locationPath = (locationPath.endsWith("/") || !StringUtils.hasLength(locationPath) ? locationPath : locationPath + "/"); if (!resourcePath.startsWith(locationPath)) { return false; } if (resourcePath.contains("%")) { // Use URLDecoder (vs UriUtils) to preserve potentially decoded UTF-8 chars... if (URLDecoder.decode(resourcePath, "UTF-8").contains("../")) { if (logger.isTraceEnabled()) { logger.trace("Resolved resource path contains \"../\" after decoding: " + resourcePath); } return false; } } return true; }

'/'表示根目錄,以'/'結尾的路徑表示文件夾,location為'/'表示該路徑下的全部文件相當於'/**','/*'表示單級目錄,所以'/'=='/**'>'/*'

mvc:resource可以設置多個屬性

在@RequestMapping注解中,如果沒有參數,則表示這個servlet是默認的servlet,會處理一切資源.

   <!-- 對靜態資源文件的訪問  方案一  -->  
    <mvc:default-servlet-handler/>  
      
    <!-- 對靜態資源文件的訪問  方案二 -->  
<mvc:resources mapping="/images/**" location="/images/" cache-period="31556926"/>  
<mvc:resources mapping="/js/**" location="/js/" cache-period="31556926"/>  
<mvc:resources mapping="/css/**" location="/css/" cache-period="31556926"/> 
<!-- 對靜態資源文件的訪問  方案二的簡便寫法--> 
 <mvc:resources location="/" mapping="/" /> 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM