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时需要特别注意这点。

猜你喜欢

转载自blog.csdn.net/mhmyqn/article/details/47750897