問題描述
使用SpringMVC時遇到靜態資源無法加載的問題,報404
問題原因
如果SpringMVC的映射模式采用的是后綴名匹配,如【*.do】或者【*.action】則不會出現該問題,因為靜態資源的訪問會由tomcat的默認Servlet,即org.apache.catalina.servlets.DefaultServlet,進行處理。
出現這種問題一般是在web.xml中的對spring的DispatcherServlet采用了如下配置,即url-pattern設置為了“/”(或"/*",這種匹配默認極其惡劣),這樣便要求SpringMVC負責處理靜態資源請求。
<!-- Spring MVC servlet --> <servlet> <servlet-name>SpringMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>SpringMVC</servlet-name> <!-- 此處可以可以配置成*.do,對應struts的后綴習慣 --> <url-pattern>/</url-pattern> </servlet-mapping>
這種情況下瀏覽器的jsp請求能夠成功是以為Tomcat容器默認配置了jsp Servlet,如下。由於url-pattern是“.jsp”,屬於擴展名匹配,優先級高於“/”匹配,所以jsp Servlet會被容器調用,不會出現jsp資源無法獲取的問題。
關於Servlet中url-pattern的配置和優先級問題,請參見博文servlet的url-pattern匹配規則。
<servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> <init-param> <param-name>fork</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>xpoweredBy</param-name> <param-value>false</param-value> </init-param> <load-on-startup>3</load-on-startup> </servlet> <!-- The mappings for the JSP servlet --> <servlet-mapping> <servlet-name>jsp</servlet-name> <url-pattern>*.jsp</url-pattern> <url-pattern>*.jspx</url-pattern> </servlet-mapping>
解決方法
1使用tomcat容器的默認servlet 處理靜態資源請求
在web.xml里添加如下的配置,由於后綴名匹配的優先級高於默認匹配規則(“/”),所以靜態資源請求會由DefaultServlet進行處理。
<servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.css</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.gif</url-pattern> </servlet-mapping> <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>
tomcat的default servlet定義如下
<servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>0</param-value> </init-param> <init-param> <param-name>listings</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
2 在spring的配置文件中添加<mvc:default-servlet-handler/>
<mvc:default-servlet-handler/>
注意,需要是spring3.0.5以上版本
該配置會在DispatcherServlet中注冊 org.springframework.web.servlet.handler.SimpleUrlHandlerMapping,該類中又注冊了訪問路徑和對應的處理類,如下圖:
/** -> org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler
即最終由DefaultServletHttpRequestHandler完成靜態資源請求處理。
這時SpringMVC便具備了處理靜態資源請求的能力。
另外需要注意的是,由於<mvc:default-servlet-handler/> 標簽注冊了SimpleUrlHandlerMapping類,所以DispatcherServlet便不再默認注冊DefaultAnnotationHandlerMapping,也就無法處理RequestMapping等注解。解決方法就是手動在配置文件中配置DefaultAnnotationHandlerMapping bean,或者添加<mvc:annotation-driven/>標簽。
3 在spring的配置文件中添加<mvc:resources mapping="" location="" />
<mvc:resources mapping="/resources/**" location="/resources/" /> <mvc:resources mapping="/images/**" location="/images/" /> <mvc:resources mapping="/js/**" location="/js/" />
該標簽和<mvc:default-servlet-handler/>類似,也會在DispatcherServlet中注冊 org.springframework.web.servlet.handler.SimpleUrlHandlerMapping,不同的是每一條配置都會注冊一個SimpleUrlHandlerMapping到DispatcherServlet中,上例會注冊三個。
每個SimpleUrlHandlerMapping內部會生成一個映射,如<mvc:resources mapping="/js/**" location="/js/" />對應的SimpleUrlHandlerMapping內部映射為:
"/js/**" -> "ResourceHttpRequestHandler
即最終由ResourceHttpRequestHandler 完成靜態資源請求的處理。
另外需要注意的是,使用<mvc:resources mapping="" location="" />后,也需要手動在配置文件中配置DefaultAnnotationHandlerMapping bean,或者添加<mvc:annotation-driven/>標簽。