spring-mvc 3.2.12及以後配置處理的變化

在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時需要特別注意這點。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章