上一篇写了使用SpringBoot访问静态文件的几种方法 ,这篇文章来讲一讲配置Springboot访问静态文件的原理,为何简单配置就能实现静态文件的加载。
我们的做法
我们是重写了WebMvcConfigurationSupport
的addResourceHandlers
方法,在registry
内配置映射关系来实现静态文件映射的。
1 2 3 4 5 6 7 8
| @Configuration public class WebConfig extends WebMvcConfigurationSupport { @Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**", "/download/**").addResourceLocations("file:F:/娱乐/","classpath:/static/"); } }
|
追踪addResourceHandlers
的父类调用关系。是一个加了@Bean
注解的方法调用了此方法。
此方法会使用我们配置的registry
创建一个HandlerMapping
处理,如果没配置则不会构造这个HandlerMapping
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @Bean @Nullable public HandlerMapping resourceHandlerMapping( @Qualifier("mvcUrlPathHelper") UrlPathHelper urlPathHelper, @Qualifier("mvcPathMatcher") PathMatcher pathMatcher, @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");
ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext, this.servletContext, contentNegotiationManager, urlPathHelper); addResourceHandlers(registry); AbstractHandlerMapping handlerMapping = registry.getHandlerMapping(); if (handlerMapping == null) { return null; } handlerMapping.setPathMatcher(pathMatcher); handlerMapping.setUrlPathHelper(urlPathHelper); handlerMapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider)); handlerMapping.setCorsConfigurations(getCorsConfigurations()); return handlerMapping; }
|
构造过程,查看上面代码的registry.getHandlerMapping()
调用。
他会根据我们的配置构造SimpleUrlHandlerMapping
,这个SimpleUrlHandlerMapping
内部保存多个HttpRequestHandler
,将每个请求路径映射成一个HttpRequestHandler
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| 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 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 { handler.afterPropertiesSet(); } catch (Throwable ex) { throw new BeanInitializationException("Failed to init ResourceHttpRequestHandler", ex); } urlMap.put(pathPattern, handler); } } return new SimpleUrlHandlerMapping(urlMap, this.order); }
|
上面的配置将断点打到这里,查看urlMap
里面的值,他的key
是拦截的路径,value
是构造的ResourceHttpRequestHandler
,这个Handler
里面配置的是两个路径分别是/static/**
和/download/**
。
值都是ResourceHttpRequestHandler实例,里面保存两个路径是file:F:/娱乐/
,classpath:/static/
。这样浏览器发送的请求如何匹配/static/**
就会被下面的handler
处理,去配置的两个存储位置查找资源。
何时调用HandlerMapping的
将断点打在Dispathervlet
的doDispatch
方法上,访问 http://localhost:8080/static/2.jpg,程序进入`getHandler()`方法里。
1 2 3 4 5 6 7 8 9 10 11 12
| protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { for (HandlerMapping mapping : this.handlerMappings) { HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } } return null; }
|
此方法内遍历的handlerMapping
如下,他会逐个调用他们的getHandler()
方法,这样就能找到对应的ResourceHttpRequestHandler
处理请求了。
ResourceHttpRequestHandler如何处理请求的
查看ResourceHttpRequestHandler
的handleRequest
方法。它调用了getResource(request)
方法获取资源。
1 2 3 4 5 6 7 8 9 10 11 12
| @Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Resource resource = getResource(request); if (resource == null) { logger.debug("Resource not found"); response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } ..... 省略
|
getResource
方法使用了责任模式,将多个location
封装到resolverChain
中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Nullable protected Resource getResource(HttpServletRequest request) throws IOException { String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE); path = processPath(path);
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; }
|
location的分类
org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry#getHandlerMapping
方法内调用了handler.afterPropertiesSet()
方法,再调用resolveResourceLocations()
方法,此方法将配置的多个路径解析成不同的Location。
1
| Resource resource = applicationContext.getResource(location);
|
请看下面代码,location
有四种分别是
ClassPathResource
从classpath获取资源
FileUrlResource
从文件获取资源
UrlResource
从url获取资源
ClassPathContextResource
委托给context获取资源
下面是将字符串解析成不同Location
的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @Override public Resource getResource(String location) { Assert.notNull(location, "Location must not be null");
for (ProtocolResolver protocolResolver : getProtocolResolvers()) { Resource resource = protocolResolver.resolve(location, this); if (resource != null) { return resource; } }
if (location.startsWith("/")) { return getResourceByPath(location); } else if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { URL url = new URL(location); return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url)); } catch (MalformedURLException ex) { return getResourceByPath(location); } } }
|
总结
Springboot
将我们配置的映射:
1
| registry.addResourceHandler("/static/**", "/download/**").addResourceLocations("file:F:/娱乐/","classpath:/static/");
|
将addResourceLocations
根据前缀解析成上面四种Location
对象,在加上配置的路径封装成ResourceHttpRequestHandler
用于处理请求,多个ResourceHttpRequestHandler
封装成SimpleUrlHandlerMapping
对象。
DispatchServlet
内能查找到SimpleUrlHandlerMapping
内的ResourceHttpRequestHandler
处理请求。
ResourceHttpRequestHandler
将内部的Location
组成责任链,按顺序查找资源。
以上就是SpringBoot
访问静态文件的原理。