ResourceHandlerRegistry
/ResourceHandlerRegistration
是Spring MVC
的概念模型类,二者配合使用。ResourceHandlerRegistry
用于保存服务静态资源图片,css
文件或者其他文件的资源处理器(resource handler
)的注册信息,而ResourceHandlerRegistration
就表示这样的"注册信息",它还包含了对头部缓存的设置,用于优化浏览器中资源的加载效率。
ResourceHandlerRegistry
/ResourceHandlerRegistration
所管理的静态资源可以是web
应用根路径下的资源,但不限于此,也可以是classpath
上的资源,文件系统的资源或者其他。
可以认为一个ResourceHandlerRegistry
组合管理了多个ResourceHandlerRegistration
对象。
ResourceHandlerRegistry
的典型用法是被某个WebMvcConfigurer
实现类用于配置静态资源服务,使用例子如下所示 :
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 静态资源文件映射配置
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 注意 :
// 1. addResourceHandler 参数可以有多个
// 2. addResourceLocations 参数可以是多个,可以混合使用 file: 和 classpath : 资源路径
// 3. addResourceLocations 参数中资源路径必须使用 / 结尾,如果没有此结尾则访问不到
// 映射到文件系统中的静态文件(应用运行时,这些文件无业务逻辑,但可能被替换或者修改)
registry.addResourceHandler("/repo/**").addResourceLocations("file:/tmp/");
// 映射到jar包内的静态文件(真正的静态文件,应用运行时,这些文件无业务逻辑,也不能被替换或者修改)
registry.addResourceHandler("/my-static/**").addResourceLocations("classpath:/my-static/");
}
// ...
}
当每次调用registry.addResourceHandler()
时,实际上它会创建一个ResourceHandlerRegistration
对象,然后将该对象放到自己的注册表管理起来,函数参数pathPatterns
表示要映射到URL pattern
, 可以传递多个要映射到的URL pattern
。如下所示 :
// ResourceHandlerRegistry 代码片段
/**
* Add a resource handler for serving static resources based on the specified URL path patterns.
* The handler will be invoked for every incoming request that matches to one of the specified
* path patterns.
* 增加一个基于指定的URL路径模式服务静态资源的资源处理器
* Patterns like "/static/**" or "/css/{filename:\\w+\\.css}" are allowed.
* pathPatterns 表示要映射到URL pattern, 可以传递多个要映射到的 URL pattern
* See org.springframework.util.AntPathMatcher for more details on the syntax.
* @return a ResourceHandlerRegistration to use to further configure the
* registered resource handler
*/
public ResourceHandlerRegistration addResourceHandler(String... pathPatterns) {
ResourceHandlerRegistration registration = new ResourceHandlerRegistration(pathPatterns);
this.registrations.add(registration);
return registration;
}
而当调用addResourceLocations()
时,ResourceHandlerRegistration
对象其实接受了一组资源路径,可能来自classpath
,也可能来自文件系统,然后它将这些路径记录下来。如下所示 :
// ResourceHandlerRegistration 代码片段
/**
* Add one or more resource locations from which to serve static content.
* 增加一个或者多个资源位置,所服务的静态资源会来自于这些资源位置。每一个资源位置可以使
* classpath 路径(classpath:/开头),也可能是本地文件系统路径(file:/开头)。
* 这些资源位置上的静态资源都会映射到本注册器所被设置的URL路径模式(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, {"/", "classpath:/META-INF/public-web-resources/"}
* allows resources to be served both from the web application root and
* from any JAR on the classpath that contains a
* /META-INF/public-web-resources/ directory, with resources in the
* web application root taking precedence.
* For org.springframework.core.io.UrlResource URL-based resources
* (e.g. files, HTTP URLs, etc) this method supports a special prefix to
* indicate the charset associated with the URL so that relative paths
* appended to it can be encoded correctly, e.g.
* [charset=Windows-31J]http://example.org/path.
* @return the same ResourceHandlerRegistration instance, for
* chained method invocation
*/
public ResourceHandlerRegistration addResourceLocations(String... resourceLocations) {
this.locationValues.addAll(Arrays.asList(resourceLocations));
return this;
}
最后生成静态资源处理器(resource handler
)时,会用到上面提到的这些URL pattern
和资源路径resource locations
。
当应用开发人员通过上面的registry.addResourceHandler("/repo/**").addResourceLocations("file:/tmp/")
这种方式配置完静态资源映射关系之后,这些关系会被ResourceHandlerRegistry registry
对象记录下来。然后在应用启动过程中,Spring MVC
配置执行阶段,具体来讲,是bean resourceHandlerMapping
实例化过程中,这些信息会被使用,如下代码所示 :
// WebMvcConfigurationSupport 代码片段
/**
* Return a handler mapping ordered at Integer.MAX_VALUE-1 with mapped
* resource handlers. To configure resource handling, override
* #addResourceHandlers.
*/
@Bean
@Nullable
public HandlerMapping resourceHandlerMapping() {
Assert.state(this.applicationContext != null, "No ApplicationContext set");
Assert.state(this.servletContext != null, "No ServletContext set");
// bean resourceHandlerMapping 实例化过程中新建一个ResourceHandlerRegistry对象
ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
this.servletContext, mvcContentNegotiationManager(), mvcUrlPathHelper());
// 新建的 ResourceHandlerRegistry 对象交给 WebMvcConfigurer 实现类的配置方法
// addResourceHandlers 来处理,开发人员会在 addResourceHandlers 中提供自己的定制逻辑
addResourceHandlers(registry);
// 这里使用addResourceHandlers定制之后的 ResourceHandlerRegistry registry
// 形成最终要使用的静态资源映射关系对象 handlerMapping, 其实是一个类型
// 为 SimpleUrlHandlerMapping 的对象,所管理的映射关系可以理解为一个 Map,
// 每一项内容是 : <URL pattern,ResourceHttpRequestHandler>
// 注意 : ResourceHandlerRegistration 构造函数如果接收多个 URL pattern,
// 则每一个 URL pattern 会对应面的一项,而不是整个 ResourceHandlerRegistration 对象
// 对应上面的一项
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
if (handlerMapping == null) {
return null;
}
handlerMapping.setPathMatcher(mvcPathMatcher());
handlerMapping.setUrlPathHelper(mvcUrlPathHelper());
handlerMapping.setInterceptors(getInterceptors());
handlerMapping.setCorsConfigurations(getCorsConfigurations());
return handlerMapping;
}
从上面代码来看,通过registry.getHandlerMapping()
方法调用,静态资源映射注册信息表ResourceHandlerRegistry
被最终转换成最终要使用的HandlerMapping
对象。其代码如下所示 :
/**
* Return a handler mapping with the mapped resource handlers; or null in case
* of no registrations.
*/
@Nullable
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);
}
}
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
handlerMapping.setOrder(this.order);
handlerMapping.setUrlMap(urlMap);
return handlerMapping;
}