springmvc對靜態資源的處理
-
<servlet>
-
<servlet-name>mvc</servlet-name>
-
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
-
<init-param>
-
<
-
<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 先攔截。
但是這樣還會有個問題,就是無法訪問到classpath下的資原始檔,看了tomcat的DefaultServlet的配置項,似乎也沒有可以指定目錄的地方。
◇ 方案二:Spring 3.0.4 以後版本提供了 <mvc:resources />
-
<!-- 處理靜態資源 -->
-
<!-- 上傳的圖片快取1個月,其他js,css,img資源快取一年 -->
-
<mvc:resources mapping="/res/**" location="/res/" cache-period="2592000"/>
-
<mvc:resources mapping="/resources/**" location="/resources/" cache-period="31536000"/>
-
<mvc:resources mapping="/css/**" location="/css/" cache-period="31536000"/>
-
<mvc:resources mapping="/js/**" location="/js/" cache-period="31536000"/>
-
<mvc:resources mapping="/img/**" location="/img/" cache-period="31536000"/>
-
<mvc:resources mapping="/images/**" location="/images/" cache-period="31536000"/>
mapping 對映到 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。
另外,效能最好的應該是直接利用容器的DefaultServlet,讓它最先攔截靜態資源請求,這樣就避免了後續的轉發等操作,提高了效能,但是無法訪問classpath下的資原始檔。而通過mvc:resources標籤可以簡單配置匹配規則和資原始檔路徑,應該說是最簡單快捷的一種方式,當然這大概也是mvc名稱空間設計的初衷。
另外,若想結合兩者的話,自己倒是可以嘗試寫一個Servlet來處理,不過估計有難度且麻煩。