在构建Spring MVC Restful风格的应用时,由于在web.xml中:
<span style="font-family:Microsoft YaHei;font-size:18px;"><servlet>
<servlet-name>story</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>story</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping></span>
拦截了所有的请求,当然也包括对静态资源的请求拦截,如页面对image,css,js文件的引用,但是并没有定义相应的Controller来对这些请求进行响应,因此这些请求通常是无法完成的。
说到这里,我们应该想一个问题:Tomcat中,只有servlet能够处理请求,即使是jsp,也会被编译成 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 来处理静态资源
<span style="font-family:Microsoft YaHei;font-size:18px;"><servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.css</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>*.jpg</url-pattern>
</servlet-mapping></span>
每种类型的静态资源需要分别配置一个servlet-mapping,同时,要写在 DispatcherServlet 的前面, 让defaultServlet先拦截。(是不是一定要放在DispatcherServlet 前,需要您验证)
但是这样还会有个问题,就是无法访问到classpath下的资源文件,看了tomcat的DefaultServlet的配置项,似乎也没有可以指定目录的地方。
方案二:Spring 3.0.4 以后版本提供了<mvc:resources/>
<!-- 处理静态资源 -->
<span style="font-family:Microsoft YaHei;font-size:18px;"><!-- 上传的图片缓存1个月,其他js,css,img资源缓存一年 -->
<mvc:resources mapping="/res/**" location="/res/" cache-period="2592000"/>
<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"/></span>
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命名空间设计的初衷。
一般情况下,如果我们使用Spring MVC来开发我们的应用的话,常用方案二来解决问题,也常使用<c:url value=""/>来引用一些资源,但对于像在css中引用的资源由于没有contextPath还是无法请求到,如body{background-image:url(../image/bg.jpg)},这时可以结合方案一,在web.xml中加入:
<span style="font-family:Microsoft YaHei;font-size:18px;"><servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.jpg</url-pattern>
</servlet-mapping></span>
备注:<c:url>标签作用是将一个URL地址格式化为一个字符串,并且保存在一个变量当中。它具有URL自动重写功能。value指定的URL可以是当前工程的一个URL地址,也可以是其他web工程的URL。但是这时需要context属性。也可以添加需要传递的参数。
属性:
var :变量名称;
value:要格式化的URL;
scope:作用域范围,默认为page;
context:其他工程路径;
<c:url var="urlStr" value="/user.jsp" >
<c:param name="id" value="111" />
</c:url>
<c:url var="urlStr" value="/user.jsp" context="/project" /><!--同一容器的其他web应用-->
<c:out value="${urlStr}" />
<a href="${urlStr}"" >测试</a>