当前位置: 代码迷 >> VC/MFC >> spring-mvc 3.2.12及之后<mvc:resource>配置处理的变化
  详细解决方案

spring-mvc 3.2.12及之后<mvc:resource>配置处理的变化

热度:171   发布时间:2016-05-02 03:53:31.0
spring-mvc 3.2.12及以后<mvc:resource>配置处理的变化

在web应用中,对于静态资源,一般不会经常变化,所以通常会使用缓存,以提高效率。在spring-mvc中提供了几种方式来处理静态资源,其中一种是通过<mvc:resource>配置资源的位置和映射的路径等信息。但是在3.2.12(包含3.2.12)以后,在配置的处理上有变化,这就是location属性不能使用通配符模式,比如<mvc:resource location=“res/**” mapping=“res/**”/>这种方式,在3.2.12以前可以正常工作,但是在3.2.12及以后,不能正常工作,如请求/res/jquery.js,在判断指定location下是否存在该资源时,解析成了/res/**/,导致在该位置下找不到资源,从而返回404错误。

下面从源码来看一下这个处理过程

首先从spring-mvc的xsd文件中可以看到<mvc:resource>的配置:

	<xsd:element name="resources">		<xsd:annotation>			<xsd:documentation				source="java:org.springframework.web.servlet.resource.ResourceHttpRequestHandler"><![CDATA[	Configures a handler for serving static resources such as images, js, and, css files with cache headers optimized for efficient	loading in a web browser. Allows resources to be served out of any path that is reachable via Spring's Resource handling.			]]></xsd:documentation>		</xsd:annotation>		<xsd:complexType>			<xsd:attribute name="mapping" use="required" type="xsd:string">				<xsd:annotation>					<xsd:documentation><![CDATA[	The URL mapping pattern, within the current Servlet context, to use for serving resources from this handler, such as "/resources/**"					]]></xsd:documentation>				</xsd:annotation>			</xsd:attribute>			<xsd:attribute name="location" use="required" type="xsd:string">				<xsd:annotation>					<xsd:documentation><![CDATA[	The resource location from which to serve static content, specified at a Spring Resource pattern.	Each location must point to a valid directory. Multiple locations may be specified as a comma-separated list,	and the locations will be checked for a given resource in the order specified. For example, a value of	"/, classpath:/META-INF/public-web-resources/" will allow resources to be served both from the web app	root and from any JAR on the classpath  that contains a /META-INF/public-web-resources/ directory,	with resources in the web app root taking precedence.					]]></xsd:documentation>				</xsd:annotation>			</xsd:attribute>			<xsd:attribute name="cache-period" type="xsd:string">				<xsd:annotation>					<xsd:documentation>						<![CDATA[	Specifies the cache period for the resources served by this resource handler, in seconds.	The default is to not send any cache headers but rather to rely on last-modified timestamps only.	Set this to 0 in order to send cache headers that prevent caching, or to a positive number of	seconds in order to send cache headers with the given max-age value.					]]></xsd:documentation>				</xsd:annotation>			</xsd:attribute>			<xsd:attribute name="order" type="xsd:int">				<xsd:annotation>					<xsd:documentation>						<![CDATA[	Specifies the order of the HandlerMapping for the resource handler. The default order is Ordered.LOWEST_PRECEDENCE - 1.					]]></xsd:documentation>				</xsd:annotation>			</xsd:attribute>		</xsd:complexType>	</xsd:element>
可以看到其中处理静态资源的类是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;	}

基于上面的源码分析,可以看出,这种处理静态资源的方式不是向前兼容的,所以在升级spring-mvc时需要特别注意这点。

版权声明:本文为博主原创文章,未经博主允许不得转载。

  相关解决方案