跳至主要內容

11. resourceHandlerMapping

安图新大约 3 分钟

11. resourceHandlerMapping

前言

resourceHandlerMapping 提供了资源的访问。

一、resourceHandlerMapping

	@Nullable
	public HandlerMapping resourceHandlerMapping(
			@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
			@Qualifier("mvcConversionService") FormattingConversionService conversionService,
			@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {



		Assert.state(this.applicationContext != null, "No ApplicationContext set");
		Assert.state(this.servletContext != null, "No ServletContext set");

		PathMatchConfigurer pathConfig = getPathMatchConfigurer();

		ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
				this.servletContext, contentNegotiationManager, pathConfig.getUrlPathHelper());
		//注册ResourceHandler
		addResourceHandlers(registry);

		//获取handlerMapping ,这里是SimpleUrlHandlerMapping,handler是ResourceHttpRequestHandler
		AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
		if (handlerMapping == null) {


			return null;
		}
		if (pathConfig.getPatternParser() != null) {


			handlerMapping.setPatternParser(pathConfig.getPatternParser());
		}
		else {


			handlerMapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());
			handlerMapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());
		}
		//拦截器
		handlerMapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
		//跨域
		handlerMapping.setCorsConfigurations(getCorsConfigurations());
		return handlerMapping;
	}

二、ResourceHttpRequestHandler

接着上一步中 registry.getHandlerMapping()分析

(1)getHandlerMapping( )

protected AbstractHandlerMapping getHandlerMapping() {


		if (this.registrations.isEmpty()) {


			return null;
		}

		Map<String, HttpRequestHandler> urlMap = new LinkedHashMap<>();
		for (ResourceHandlerRegistration registration : this.registrations) {


			for (String pathPattern : registration.getPathPatterns()) {


				//获取ResourceHttpRequestHandler
				ResourceHttpRequestHandler handler = registration.getRequestHandler();
				if (this.pathHelper != null) {


					handler.setUrlPathHelper(this.pathHelper);
				}
				if (this.contentNegotiationManager != null) {


					handler.setContentNegotiationManager(this.contentNegotiationManager);
				}
				handler.setServletContext(this.servletContext);
				handler.setApplicationContext(this.applicationContext);
				try {


					//调用afterPropertiesSet()
					handler.afterPropertiesSet();
				}
				catch (Throwable ex) {


					throw new BeanInitializationException("Failed to init ResourceHttpRequestHandler", ex);
				}
				urlMap.put(pathPattern, handler);
			}
		}

		//构建SimpleUrlHandlerMapping
		return new SimpleUrlHandlerMapping(urlMap, this.order);
	}

(2)getRequestHandler( )

protected ResourceHttpRequestHandler getRequestHandler() {


		//创建ResourceHttpRequestHandler
		ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
		if (this.resourceChainRegistration != null) {



		    //填充属性
			handler.setResourceResolvers(this.resourceChainRegistration.getResourceResolvers());
			handler.setResourceTransformers(this.resourceChainRegistration.getResourceTransformers());
		}
		//locationValues保存着资源的路径
		handler.setLocationValues(this.locationValues);
		handler.setLocations(this.locationsResources);
		if (this.cacheControl != null) {


			handler.setCacheControl(this.cacheControl);
		}
		else if (this.cachePeriod != null) {


			handler.setCacheSeconds(this.cachePeriod);
		}
		handler.setUseLastModified(this.useLastModified);
		return handler;
	}

三、HttpRequestHandlerAdapter

(1)handle( )

HttpRequestHandlerAdapter.java

public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {



		((HttpRequestHandler) handler).handleRequest(request, response);
		return null;
	}

(2)handleRequest( )

ResourceHttpRequestHandler.java

public void handleRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {



		// For very general mappings (e.g. "/") we need to check 404 first
		//获取请求资源
		Resource resource = getResource(request);
		if (resource == null) {


			logger.debug("Resource not found");
			response.sendError(HttpServletResponse.SC_NOT_FOUND);
			return;
		}

		if (HttpMethod.OPTIONS.matches(request.getMethod())) {


			response.setHeader("Allow", getAllowHeader());
			return;
		}

		// Supported methods and required session
		checkRequest(request);

		// Header phase
		//校验资源有没有变化过
		if (isUseLastModified() && new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) {


			logger.trace("Resource not modified");
			return;
		}

		// Apply cache settings, if any
		prepareResponse(response);

		// Check the media type for the resource
		MediaType mediaType = getMediaType(request, resource);
		setHeaders(response, resource, mediaType);

		// Content phase
		ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
		if (request.getHeader(HttpHeaders.RANGE) == null) {


			Assert.state(this.resourceHttpMessageConverter != null, "Not initialized");
			this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
		}
		else {


			Assert.state(this.resourceRegionHttpMessageConverter != null, "Not initialized");
			ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(request);
			try {


				List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
				response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
				this.resourceRegionHttpMessageConverter.write(
						HttpRange.toResourceRegions(httpRanges, resource), mediaType, outputMessage);
			}
			catch (IllegalArgumentException ex) {


				response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
				response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
			}
		}
	}

(3)getResource( )

ResourceHttpRequestHandler.java

@Nullable
	protected Resource getResource(HttpServletRequest request) throws IOException {


		String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
		if (path == null) {


			throw new IllegalStateException("Required request attribute '" +
					HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE + "' is not set");
		}

		//处理路径
		path = processPath(path);
		if (!StringUtils.hasText(path) || isInvalidPath(path)) {


			return null;
		}
		if (isInvalidEncodedPath(path)) {


			return null;
		}

		Assert.notNull(this.resolverChain, "ResourceResolverChain not initialized.");
		Assert.notNull(this.transformerChain, "ResourceTransformerChain not initialized.");
		//根据设置的路径找到资源
		Resource resource = this.resolverChain.resolveResource(request, path, getLocations());
		if (resource != null) {


			resource = this.transformerChain.transform(request, resource);
		}
		return resource;
	}

(4)resolveResource( )

DefaultResourceResolverChain.java

public Resource resolveResource(
			@Nullable HttpServletRequest request, String requestPath, List<? extends Resource> locations) {



		return (this.resolver != null && this.nextChain != null ?
				this.resolver.resolveResource(request, requestPath, locations, this.nextChain) : null);
	}

(5)最终通过 PathResourceResolver 获取请求资源

private Resource getResource(String resourcePath, @Nullable HttpServletRequest request,
			List<? extends Resource> locations) {



		for (Resource location : locations) {


			try {


				//
				String pathToUse = encodeOrDecodeIfNecessary(resourcePath, request, location);
				Resource resource = getResource(pathToUse, location);
				if (resource != null) {


					return resource;
				}
			}
			catch (IOException ex) {


				if (logger.isDebugEnabled()) {


					String error = "Skip location [" + location + "] due to error";
					if (logger.isTraceEnabled()) {


						logger.trace(error, ex);
					}
					else {


						logger.debug(error + ": " + ex.getMessage());
					}
				}
			}
		}
		return null;
	}

(6)getResource( )

protected Resource getResource(String resourcePath, Resource location) throws IOException {


		Resource resource = location.createRelative(resourcePath);
		if (resource.isReadable()) {


			if (checkResource(resource, location)) {


				return resource;
			}
			else if (logger.isWarnEnabled()) {


				Resource[] allowedLocations = getAllowedLocations();
				logger.warn("Resource path \"" + resourcePath + "\" was successfully resolved " +
						"but resource \"" +	resource.getURL() + "\" is neither under the " +
						"current location \"" + location.getURL() + "\" nor under any of the " +
						"allowed locations " + (allowedLocations != null ? Arrays.asList(allowedLocations) : "[]"));
			}
		}
		return null;
	}

(7)createRelative( )

通过 ClassPathResource 获取资源

public Resource createRelative(String relativePath) {


		String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
		return (this.clazz != null ? new ClassPathResource(pathToUse, this.clazz) :
				new ClassPathResource(pathToUse, this.classLoader));
	}

四、示例

@Configuration
public class ResourceHandlerConfig implements WebMvcConfigurer {



    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {


        registry.
                addResourceHandler("/swagger-ui/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
                .resourceChain(false);
    }
}

当访问以/swagger-ui/开头的路径下资源时,会在 classpath:/META-INF/resources/webjars/springfox-swagger-ui/路径下查找资源。


总结

通过 addResourceHandlers 注册资源,通过 ClassPathResource 获取资源。