@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class})
// 当容器中不存在WebMvcConfigurationSupport的时候,如果手动添加了@EnableMvc注解即失效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class})
public class WebMvcAutoConfiguration {
class ResourceProperties {
// 默认的静态资源路径
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {"classpath:/META-INF/resources/",
"classpath:/resources/", "classpath:/static/", "classpath:/public/"};
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
}
class WebMvcProperties {
// servlet相关配置
private final Servlet servlet = new Servlet();
// view相关配置
private final View view = new View();
class Servlet {
// 请求根路径
private String path = "/";
// 启动时机
private int loadOnStartup = -1;
}
class View {
// 视图名前缀
private String prefix;
// 视图名后缀
private String suffix;
}
}
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({WebMvcProperties.class, ResourceProperties.class})
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
// 添加静态资源处理器
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 从配置文件中获取属性
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
// 添加wabjar的资源映射
// webjars: 将各种js包打包成的jar,在前后端不分离的时候好用
// 只需要引入webjar的依赖,然后配置该资源不拦截
if (!registry.hasMappingForPattern("/webjars/**")) {
ResourceHandlerRegistration registration = registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/")
.setCachePeriod(getSeconds(cachePeriod))
.setCacheControl(cacheControl);
// 使用ResourceHandlerRegistrationCustomizer类型的Bean对该资源处理器自定义
this.customizeResourceHandlerRegistration(registration);
}
// 获取mvc配置的属性,静态资源路径
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
// 添加指定的静态资源路径的资源处理器
if (!registry.hasMappingForPattern(staticPathPattern)) {
ResourceHandlerRegistration registration = registry.addResourceHandler(staticPathPattern)
.addResourceLocations(this.getResourceLocations(this.resourceProperties.getStaticLocations()))
.setCachePeriod(this.getSeconds(cachePeriod))
.setCacheControl(cacheControl);
// 使用ResourceHandlerRegistrationCustomizer类型的Bean对该资源处理器自定义
this.customizeResourceHandlerRegistration(registration);
}
}
}
}
class DelegatingWebMvcConfiguration {
private final List<WebMvcConfigurer> configurers = new ArrayList<>();
@Bean
public HandlerMapping resourceHandlerMapping(
@Qualifier("mvcUrlPathHelper") UrlPathHelper urlPathHelper,
@Qualifier("mvcPathMatcher") PathMatcher pathMatcher,
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
// 资源处理器注册表
ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext, this.servletContext, contentNegotiationManager, urlPathHelper);
// 执行WebMvcConfigurer接口的addResourceHandlers,添加资源处理器
this.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;
}
// 执行WebMvcConfigurer接口的addResourceHandlers,添加资源处理器
// 这个添加了资源处理器才会有后续的处理逻辑
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
this.configurers.addResourceHandlers(registry);
}
}
// 资源处理器注册表
class ResourceHandlerRegistry {
// 资源处理器
private final List<ResourceHandlerRegistration> registrations = new ArrayList<>();
/**
* 生成HandlerMapping对象
* {@link ResourceHttpRequestHandler}
*/
protected AbstractHandlerMapping getHandlerMapping() {
// 如果没有资源处理器,就不处理
if (this.registrations.isEmpty()) {
return null;
}
// url与HttpRequestHandler(处理请求的Handler)的映射
Map<String, HttpRequestHandler> urlMap = new LinkedHashMap<>();
// 遍历所有的资源处理器
for (ResourceHandlerRegistration registration : this.registrations) {
// 获取该处理器处理的路径
for (String pathPattern : registration.getPathPatterns()) {
// 获取该处理器处理该请求路径的Handler
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);
// 执行handler的初始化方法
handler.afterPropertiesSet();
// 保存静态资源url -> 处理该静态资源请求的Handler
urlMap.put(pathPattern, handler);
}
}
// 注册一个HandlerMapping,专门处理静态资源的请求
return new SimpleUrlHandlerMapping(urlMap, this.order);
}
// 添加资源处理器
public ResourceHandlerRegistration addResourceHandler(String... pathPatterns) {
// 创建指定路径的资源处理器
ResourceHandlerRegistration registration = new ResourceHandlerRegistration(pathPatterns);
// 保存该处理器
this.registrations.add(registration);
// 返回创建的处理器
return registration;
}
}
// 指定路径的资源处理器
class ResourceHandlerRegistration {
// 该资源处理器处理的请求路径
private final String[] pathPatterns;
// pathPatterns映射到的资源路径
private final List<String> locationValues = new ArrayList<>();
public ResourceHandlerRegistration(String... pathPatterns) {
this.pathPatterns = pathPatterns;
}
// 获取请求的处理器HttpRequestHandler
protected ResourceHttpRequestHandler getRequestHandler() {
// 创建处理Resource类型的Handler
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
// 如果设置了资源链,也就是配置要使用的资源解析器和转换器链
if (this.resourceChainRegistration != null) {
// 设置该资源链中设置资源解析器和转换器
handler.setResourceResolvers(this.resourceChainRegistration.getResourceResolvers());
handler.setResourceTransformers(this.resourceChainRegistration.getResourceTransformers());
}
// 设置请求路径映射到的资源路径
handler.setLocationValues(this.locationValues);
// 如果存在缓存控制器
if (this.cacheControl != null) {
handler.setCacheControl(this.cacheControl);
}
// 如果不存在缓存控制器,设置缓存的有效事件
else if (this.cachePeriod != null) {
handler.setCacheSeconds(this.cachePeriod);
}
// 返回处理静态资源路径的Handler
return handler;
}
// 添加该处理器可以处理的资源路径
public ResourceHandlerRegistration addResourceLocations(String... resourceLocations) {
this.locationValues.addAll(Arrays.asList(resourceLocations));
return this;
}
}
// 路径资源解析器
class PathResourceResolver {
// 该解析器可以处理的资源对象
private Resource[] allowedLocations;
// 设置该解析器可以解析的资源对象
public void setAllowedLocations(@Nullable Resource... locations) {
this.allowedLocations = locations;
}
// 解析资源
protected Resource resolveResourceInternal(@Nullable HttpServletRequest request, String requestPath, List<? extends Resource> locations, ResourceResolverChain chain) {
return this.getResource(requestPath, request, locations);
}
// 根据路径,从给定的所有资源中,找到符合该资源路径的资源对象
private Resource getResource(String resourcePath, @Nullable HttpServletRequest request, List<? extends Resource> locations) {
// 遍历给定的所有资源对象
for (Resource location : locations) {
// url编码
String pathToUse = encodeIfNecessary(resourcePath, request, location);
// 根据自定路径,校验给定的资源对象是否符合路径的条件,如果符合,返回该资源对象,否则返回null
Resource resource = this.getResource(pathToUse, location);
if (resource != null) {
return resource;
}
}
return null;
}
// 根据自定路径,校验给定的资源对象是否符合路径的条件,如果符合,返回该资源对象,否则返回null
protected Resource getResource(String resourcePath, Resource location) throws IOException {
// 根据路径创建一个资源对象
Resource resource = location.createRelative(resourcePath);
// 资源是否可读
if (resource.isReadable()) {
// 校验该资源对象是否可用,说白了就是判断该路径的资源是不是与给的资源一致,或者有层级关系
if (this.checkResource(resource, location)) {
// 如果资源可以用,直接返回该资源对象
return resource;
}
}
return null;
}
}
/**
* 处理静态资源请求的Hanlder
*
* @see {@link ResourceHandlerRegistry#getHandlerMapping()}
*/
class ResourceHttpRequestHandler {
// 给当前handler设置的处理的资源路径
private final List<String> locationValues = new ArrayList<>(4);
// 给当前handler设置的处理的资源路径对应的Resource对象
private final List<Resource> locations = new ArrayList<>(4);
// 对Resource资源解析的解析器
private final List<ResourceResolver> resourceResolvers = new ArrayList<>(4);
// 处理请求
public void handleRequest(HttpServletRequest request, HttpServletResponse response) {
// 通过请求获取该请求中需要方法的资源路径对应的资源对象
Resource resource = this.getResource(request);
// 未找到资源
if (resource == null) {
// 响应错误
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
// 不支持的请求方法
if (HttpMethod.OPTIONS.matches(request.getMethod())) {
response.setHeader("Allow", getAllowHeader());
return;
}
// 存在该资源的缓存,该资源没有变化,直接返回
if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) {
return;
}
// 预处理响应体
this.prepareResponse(response);
// 通过contentNegotiationStrategy查找策略确定媒体类型
// 其中PathExtensionContentNegotiationStrategy通过路径扩展名获取媒体类型
MediaType mediaType = this.getMediaType(request, resource);
ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
// 请求头存在range
if (request.getHeader(HttpHeaders.RANGE) == null) {
// 设置请求头
setHeaders(response, resource, mediaType);
// 通过资源类型消息转换器将消息写入响应体中
this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
return;
}
// 如果不存在range,则使用处理ResourceRegion类型的消息转换器将资源写入响应体
response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(request);
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
this.resourceRegionHttpMessageConverter.write(HttpRange.toResourceRegions(httpRanges, resource), mediaType, outputMessage);
}
// 通过请求获取该请求中需要方法的资源路径对应的资源对象
protected Resource getResource(HttpServletRequest request) throws IOException {
// 获取路径
String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
// 解析路径
path = processPath(path);
// 使用资源解析器,将设置的资源路径解析成Resource对象
Resource resource = this.resolverChain.resolveResource(request, path, this.locations);
// 如果解析到了
if (resource != null) {
// 在通过资源转换器执行链进行加工
resource = this.transformerChain.transform(request, resource);
}
return resource;
}
// 对象初始化
public void afterPropertiesSet() throws Exception {
// 解析资源路径,将路径对应的Resource获取出来
this.resolveResourceLocations();
// 如果不存在资源解析器
if (this.resourceResolvers.isEmpty()) {
// 添加一个路径的资源解析器
this.resourceResolvers.add(new PathResourceResolver());
}
// 初始化路径资源解析器可以处理哪些路径的资源
this.initAllowedLocations();
// 初始化资源解析器执行链
this.resolverChain = new DefaultResourceResolverChain(this.resourceResolvers);
// 初始化资源转换器执行链
this.transformerChain = new DefaultResourceTransformerChain(this.resolverChain, this.resourceTransformers);
// 设置处理资源类型Resource的消息转换器
if (this.resourceHttpMessageConverter == null) {
// 该转换器就是将请求体中的数据解析成Resource对象,将Resource对象写入响应体
// 静态资源文件就是这样写入到相应体中
this.resourceHttpMessageConverter = new ResourceHttpMessageConverter();
}
// 处理资源类型ResourceRegion的消息转换器,和ResourceHttpMessageConverter一样,就是处理的类型为ResourceRegion
if (this.resourceRegionHttpMessageConverter == null) {
this.resourceRegionHttpMessageConverter = new ResourceRegionHttpMessageConverter();
}
// 解析所媒体类型请求中的策略
this.contentNegotiationStrategy = this.initContentNegotiationStrategy();
}
// 将请求路径中的文件扩展名解析为用于查找媒体类型的键
protected PathExtensionContentNegotiationStrategy initContentNegotiationStrategy() {
// 获取媒体类型
Map<String, MediaType> mediaTypes = null;
// 如果存在管理器
if (this.contentNegotiationManager != null) {
// 通过管理器获取处理媒体类型的策略,PathExtensionContentNegotiationStrategy通过路径扩展名获取媒体类型
PathExtensionContentNegotiationStrategy strategy = this.contentNegotiationManager.getStrategy(PathExtensionContentNegotiationStrategy.class);
// 如果找到合适的策略,则获取该策略支持的媒体类型
if (strategy != null) {
mediaTypes = new HashMap<>(strategy.getMediaTypes());
}
}
// 如果存在ServeltContext,返回ServletPathExtensionContentNegotiationStrategy,否则返回PathExtensionContentNegotiationStrategy
// ServletPathExtensionContentNegotiationStrategy继承PathExtensionContentNegotiationStrategy,都是通过路径的扩展名
return (getServletContext() != null ?
new ServletPathExtensionContentNegotiationStrategy(getServletContext(), mediaTypes) :
new PathExtensionContentNegotiationStrategy(mediaTypes));
}
// 初始化路径资源解析器可以处理哪些路径的资源
protected void initAllowedLocations() {
// 如果该HttpRequestHandler没有可处理的资源
if (CollectionUtils.isEmpty(this.locations)) {
return;
}
// 遍历所有的资源解析器
for (int i = this.resourceResolvers.size() - 1; i >= 0; i--) {
// 如果资源解析器为路径资源解析器
if (this.resourceResolvers.get(i) instanceof PathResourceResolver) {
PathResourceResolver pathResolver = (PathResourceResolver) this.resourceResolvers.get(i);
// 获取该资源解析器支持的路径,如果为空
if (ObjectUtils.isEmpty(pathResolver.getAllowedLocations())) {
// 将当前HttpRequestHandler保存的资源设置为该路径解析器可以处理的路径
pathResolver.setAllowedLocations(this.locations.toArray(new Resource[0]));
}
// 设置字符集合url工具类
if (this.urlPathHelper != null) {
pathResolver.setLocationCharsets(this.locationCharsets);
pathResolver.setUrlPathHelper(this.urlPathHelper);
}
break;
}
}
}
// 解析资源路径,将路径对应的Resource获取出来
private void resolveResourceLocations() {
// 获取上下文对象
ApplicationContext applicationContext = obtainApplicationContext();
// 遍历设置的资源路径
for (String location : this.locationValues) {
// 使用占位符解析路径
if (this.embeddedValueResolver != null) {
String resolvedLocation = this.embeddedValueResolver.resolveStringValue(location);
location = resolvedLocation;
}
Charset charset = null;
location = location.trim();
// 资源路径是不是设置了"[charset="字符集
if (location.startsWith(URL_RESOURCE_CHARSET_PREFIX)) {
int endIndex = location.indexOf(']', URL_RESOURCE_CHARSET_PREFIX.length());
String value = location.substring(URL_RESOURCE_CHARSET_PREFIX.length(), endIndex);
// 设置字符编码
charset = Charset.forName(value);
location = location.substring(endIndex + 1);
}
// 根据路径获取实际的资源
Resource resource = applicationContext.getResource(location);
// 保存该资源对象
this.locations.add(resource);
// 保存字符集对象
if (charset != null) {
this.locationCharsets.put(resource, charset);
}
}
}
}
SpringBoot处理静态资源映射实现原理
最新推荐文章于 2024-09-09 15:56:42 发布