springmvc–静态资源的处理
文章目录
1 简单介绍
对于web
应用,静态资源处理是很重要的一环。springmvc
提供了三种方式来处理静态资源
-
在
XML
文件中配置<mvc:default-servlet-handler/>
。这种情况的原理大概如下:检查所有请求,然后判断有没有对应的@RequestMapping
映射,没有就将该请求转发给Tomcat
默认的Servlet
处理。Tomcat
默认的Servlet
只能处理项目根路径下的资源。 -
直接配置资源请求的路径,这些请求特殊处理。此种方式配置可以由用户随意指定静态资源位置,十分方便,推荐使用。如下所示,
springmvc
会将以css
开头的请求路径截取掉css/
,得到剩下的路径,然后再去类路径下的/static/css/
文件夹下匹配<mvc:resources mapping="/css/**" location="classpath:/static/css/" cache-period="31556926" /> <!--一般一种静态资源类型配置一个标签--> <mvc:resources mapping="/js/**" location="classpath:/static/js/" cache-period="31556926" />
-
将
DispatcherServlet
的拦截路径修改为/*.do
。此种方式本质上还是将静态资源请求交由Tomcat
默认的Servlet
处理。
2 转发给Tomcat
默认的Servlet
处理
我们来跟踪一下处理过程。在这种方式下,用户发送的所有请求还是先被DispatcherServlet
处理,那么直接定位到doDispatch()
方法
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
/**
* 仍然是要先获取处理器执行链
* 不过此时,相比于以前就有些区别了,使用的不是RequestMappingHandlerMapping
* 而是SimpleUrlHandlerMapping,见2.1
*/
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
//获取对应的处理器适配器,见2.2
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
//处理器适配器调用处理器处理请求,见2.3
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
2.1 获取能够处理静态资源请求的处理器执行链
/**
* Return the HandlerExecutionChain for this request.
* <p>Tries all handler mappings in order.
* @param request current HTTP request
* @return the HandlerExecutionChain, or {@code null} if no handler could be found
*/
@Nullable
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;
}
DispatcherServlet
默认持有3
个HandlerMapping
对象,按顺序分别如下所示
RequestMappingHandlerMapping
BeanNameUrlHandlerMapping
SimpleUrlHandlerMapping
第一种我们很了解,我们并没有配置静态资源请求的
@RequestMapping
映射,所以第一种不能处理,第二种我们也没有配置,那么就只剩下第三种了,执行getHandler()
方法,会得到一个处理器为DefaultServletHttpRequestHandler
对象的处理器执行链,内部结构如下所示
2.2 获取对应的处理器适配器
得到处理器执行链之后,下一步就是获得对应的处理器适配器了
/**
* Return the HandlerAdapter for this handler object.
* @param handler the handler object to find an adapter for
* @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
*/
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
DispatcherServlet
默认持有3
个HandlerAdapter
对象,按顺序分别如下所示
RequestMappingHandlerAdapter
与RequestMappingHandlerMapping
对应HttpRequestHandlerAdapter
只能处理实现了HttpRequestHandler
接口的处理器SimpleControllerHandlerAdapter
只能处理实现了Controller
接口的处理器
DefaultServletHttpRequestHandler
实现了HttpRequestHandler
接口,所以此处得到的处理器适配器为HttpRequestHandlerAdapter
2.3 处理器适配器调用处理器处理请求
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//调用处理器的handleRequest()方法处理请求
((HttpRequestHandler) handler).handleRequest(request, response);
return null;
}
处理器我们知道,它的真实类型是
DefaultServletHttpRequestHandler
,它实现了HttpRequestHandler
接口,在接口方法中完成请求转发到转发给Tomcat
默认的Servlet
/**
* 默认为default,SimpleUrlHandlerMapping获取时给定的初始值
* Tomcat默认Servlet的名字就是default
* 可在XML中手动指定名字<mvc:default-servlet-handler default-servlet-name="default"/>
* 应为你的Servlet容器可能不是Tomcat
*/
private String defaultServletName;
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Assert.state(this.servletContext != null, "No ServletContext set");
//获取转发给对应Servlet的请求转发器
RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName);
if (rd == null) {
throw new IllegalStateException("A RequestDispatcher could not be located for the default servlet '" +
this.defaultServletName + "'");
}
//转发请求
rd.forward(request, response);
}
通过执行
DefaultServletHttpRequestHandler
的handleRequest()
方法,请求就被转发到了Tomcat
的默认Servlet
处理
3 配置资源请求的路径,放行资源请求
一般我们会在springmvc
的配置文件中配置如下所示的资源路径,当请求路径是以css/
或js/
开头时,就表明这是一个静态资源请求,于是就会去location
中指定的文件夹下匹配对应的静态资源
<mvc:resources mapping="/css/**"
location="classpath:/static/css/"
cache-period="31556926" />
<!--一般一种静态资源类型配置一个标签-->
<mvc:resources mapping="/js/**"
location="classpath:/static/js/"
cache-period="31556926" />
接下来我们来跟踪一下处理过程,在这种方式下,用户发送的所有请求还是先被DispatcherServlet
处理,那么直接定位到doDispatch()
方法
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
/**
* 仍然是要先获取处理器执行链
* 不过此时,相比于以前就有些区别了,使用的不是RequestMappingHandlerMapping
* 而是SimpleUrlHandlerMapping,见3.1
*/
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
//获取对应的处理器适配器,见3.2
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
//处理器适配器调用处理器处理请求,见3.3
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
3.1 获取能够处理静态资源请求的处理器执行链
/**
* Return the HandlerExecutionChain for this request.
* <p>Tries all handler mappings in order.
* @param request current HTTP request
* @return the HandlerExecutionChain, or {@code null} if no handler could be found
*/
@Nullable
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;
}
DispatcherServlet
持有4
个HandlerMapping
对象,按顺序分别如下所示
RequestMappingHandlerMapping
BeanNameUrlHandlerMapping
SimpleUrlHandlerMapping
对应处理css
开头的请求SimpleUrlHandlerMapping
对应处理js
开头的请求这里为什么有两个
SimpleUrlHandlerMapping
呢?
- 其实项目启动扫描
springmvc
配置文件的时候,扫描到一个<mvc:resources />
标签以后,就会解析生成一个SimpleUrlHandlerMapping
类的BeanDefinition
,现在我们配置了两个<mvc:resources />
标签,这就与上面有两个SimpleUrlHandlerMapping
对象对应起来了。refresh()
方法调用结束之前,会发布一个ContextRefreshedEvent
事件,表明上下文刷新成功,此时容器中的ContextRefreshListener
就会监听到这个事件,然后将springmvc
的9
大组件设置到DispatcherServlet
中接下来我们来看一下这两个
SimpleUrlHandlerMapping
字段值
css
对应的SimpleUrlHandlerMapping
js
对应的SimpleUrlHandlerMapping
执行
getHandler()
方法,会得到一个处理器为ResourceHttpRequestHandler
对象的处理器执行链,内部结构如下所示
3.2 获取对应的处理器适配器
得到处理器执行链之后,下一步就是获得对应的处理器适配器了
/**
* Return the HandlerAdapter for this handler object.
* @param handler the handler object to find an adapter for
* @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
*/
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
DispatcherServlet
默认持有3
个HandlerAdapter
对象,按顺序分别如下所示
RequestMappingHandlerAdapter
与RequestMappingHandlerMapping
对应HttpRequestHandlerAdapter
只能处理实现了HttpRequestHandler
接口的处理器SimpleControllerHandlerAdapter
只能处理实现了Controller
接口的处理器
ResourceHttpRequestHandler
实现了HttpRequestHandler
接口,所以此处得到的处理器适配器为HttpRequestHandlerAdapter
,这个过程和2.2
中的完全一样
3.3 处理器适配器调用处理器处理请求
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//调用处理器的handleRequest()方法处理请求
((HttpRequestHandler) handler).handleRequest(request, response);
//返回一个空的模型视图对象,后面的处理就基本上跳过了
return null;
}
处理器我们知道,它的真实类型是
ResourceHttpRequestHandler
,它实现了HttpRequestHandler
接口,详细处理过程见5.2
总结一下这种方式的静态资源处理
- 静态资源请求仍然是由
DispatcherServlet
处理- 具体的处理过程交给了
ResourceHttpRequestHandler
的handleRequest()
方法
4 SimpleUrlHandlerMapping
先来看一下它的类图
HandlerMapping
最重要的方法就是getHandler(HttpServletRequest)
方法,查看该方法源码发现,该方具有唯一的实现
/**
* Look up a handler for the given request, falling back to the default
* handler if no specific one is found.
* @param request current HTTP request
* @return the corresponding handler instance, or the default handler
* @see #getHandlerInternal
*/
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//获取处理本次请求的处理器,见4.1
Object handler = getHandlerInternal(request);
//获取默认的处理器,见4.2.3
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
/**
* 未实例化就实例化
* 此处必然不会执行,因为getHandlerInternal(request)方法中已经判断过一次
* 并且它返回的是处理器执行器链,并非是一个单纯的处理器
*/
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
logger.debug("Mapped to " + executionChain.getHandler());
}
//cors相关处理,以后再说
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
这个方法流程应该不陌生吧,在springmvc–HandlerMapping处理器映射器这篇文章中详细剖析过方法源码,当时是
RequestMappingHandlerMapping
,现在是SimpleUrlHandlerMapping
,接下来我们就来看一下具体流程吧
4.1 获取处理本次请求的处理器
/**
* Look up a handler for the URL path of the given request.
* @param request current HTTP request
* @return the handler instance, or {@code null} if none found
*/
@Override
@Nullable
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
//获取本次请求路径,去掉项目名之后的路径
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
/**
* 保存到请求域org.springframework.web.servlet.HandlerMapping.lookupPath属性中
* 后面在4.2中需要取出这个路径,然后根据这个路径去匹配拦截器
*/
request.setAttribute(LOOKUP_PATH, lookupPath);
//根据路径构建对应的处理器执行器链对象,见4.1.1
Object handler = lookupHandler(lookupPath, request);
//找不到对应路径的处理器
if (handler == null) {
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
Object rawHandler = null;
//根路径就使用根路径处理器
if (StringUtils.matchesCharacter(lookupPath, '/')) {
//获取根路径的处理器,见4.1.2
rawHandler = getRootHandler();
}
//否则使用默认的处理器
if (rawHandler == null) {
//获取默认的处理器,见4.1.3
rawHandler = getDefaultHandler();
}
//将处理器实例化然后构造为处理器执行器链
if (rawHandler != null) {
// Bean name or resolved handler?
if (rawHandler instanceof String) {
String handlerName = (String) rawHandler;
rawHandler = obtainApplicationContext().getBean(handlerName);
}
validateHandler(rawHandler, request);
//构建处理器执行器链,见4.1.1.1
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
return handler;
}
该方法根据请求路径获取到对应的处理器,然后包装为处理器执行器链
- 直接根据
<mvc:resources mapping="/xxx/**" >
标签配置的进行匹配- 匹配不到再判断路径是否为根路径,根路径就使用根路径处理器(需要用户配置)
- 也不是根路径,就使用默认的处理器(需要用户配置)
- 所有的处理器均需要实现
HttpHandleRequest
接口
4.1.1 根据路径构建对应的处理器执行器链对象
/**
* 我们在<mvc resource>标签中的配置会被解析,然后保存在此处
* 比如我们配置mapping="/js/**" location="classpath:/static/js/"
* 经过解析就会在这个集合中放入一个"/js/**"->ResourceHttpRequestHandler的映射
*/
private final Map<String, Object> handlerMap = new LinkedHashMap<>();
/**
* Look up a handler instance for the given URL path.
* <p>Supports direct matches, e.g. a registered "/test" matches "/test",
* and various Ant-style pattern matches, e.g. a registered "/t*" matches
* both "/test" and "/team". For details, see the AntPathMatcher class.
* <p>Looks for the most exact pattern, where most exact is defined as
* the longest path pattern.
* @param urlPath the URL the bean is mapped to
* @param request current HTTP request (to expose the path within the mapping to)
* @return the associated handler instance, or {@code null} if not found
* @see #exposePathWithinMapping
* @see org.springframework.util.AntPathMatcher
*/
@Nullable
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// Direct match?
//先根据路径直接匹配
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
// Bean name or resolved handler?
//处理器还未实例化,先实例化
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
//校验处理器,空方法,直接忽略
validateHandler(handler, request);
//构建一个处理器执行器对象,见4.1.1.1
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
// Pattern match?
//不能直接匹配
List<String> matchingPatterns = new ArrayList<>();
for (String registeredPattern : this.handlerMap.keySet()) {
/**
* getPathMatcher()方法,获取路径匹配器
* match(registeredPattern, urlPath)方法,匹配两个路径
*/
if (getPathMatcher().match(registeredPattern, urlPath)) {
matchingPatterns.add(registeredPattern);
}
//不匹配,就判断是否开启尾部斜杠匹配,然后进行尾部斜杆匹配,见4.1.1.3
else if (useTrailingSlashMatch()) {
if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
matchingPatterns.add(registeredPattern + "/");
}
}
}
String bestMatch = null;
//路径比较器
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
//使用路径比较器对多个匹配的路径进行排序,然后使用第一个匹配的路径
if (!matchingPatterns.isEmpty()) {
matchingPatterns.sort(patternComparator);
if (logger.isTraceEnabled() && matchingPatterns.size() > 1) {
logger.trace("Matching patterns " + matchingPatterns);
}
bestMatch = matchingPatterns.get(0);
}
if (bestMatch != null) {
//获取第一个匹配路径对应的ResourceHttpRequestHandler
handler = this.handlerMap.get(bestMatch);
//以斜杠结束,就去掉斜杠,再匹配
if (handler == null) {
if (bestMatch.endsWith("/")) {
handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
}
if (handler == null) {
throw new IllegalStateException(
"Could not find handler for best pattern match [" + bestMatch + "]");
}
}
// Bean name or resolved handler?
//从容器中获取对应名字的处理器对象
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
//校验处理器,空方法,直接忽略
validateHandler(handler, request);
/**
* 截取掉请求路径中匹配的路径
* 例如js/**和js/test.js,就会得到test.js
*/
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);
// There might be multiple 'best patterns', let's make sure we have the correct URI template variables
// for all of them
//模板变量
Map<String, String> uriTemplateVariables = new LinkedHashMap<>();
for (String matchingPattern : matchingPatterns) {
if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
//得到模板变量的映射关系
Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
//解码
Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
//放入模板变量集合中
uriTemplateVariables.putAll(decodedVars);
}
}
if (logger.isTraceEnabled() && uriTemplateVariables.size() > 0) {
logger.trace("URI variables " + uriTemplateVariables);
}
//构建一个处理器执行器对象,见4.1.1.1
return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
}
// No handler found...
return null;
}
总结:
- 使用
AntPathMatcher
进行路径匹配- 可以解析路径上的模板变量,也就是说可以在
<mvc resources mapping="/js/{xxx}">
中配置模板变量- 匹配到处理器后,就将处理器包装为一个处理器执行器链,里面添加了两个默认的拦截器
PathExposingHandlerInterceptor
:将解析好的请求路径保存到请求域中,见9UriTemplateVariablesHandlerInterceptor
4.1.1.1 构建一个处理器执行器链对象
/**
* Build a handler object for the given raw handler, exposing the actual
* handler, the {@link #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE}, as well as
* the {@link #URI_TEMPLATE_VARIABLES_ATTRIBUTE} before executing the handler.
* <p>The default implementation builds a {@link HandlerExecutionChain}
* with a special interceptor that exposes the path attribute and uri template variables
* @param rawHandler the raw handler to expose
* @param pathWithinMapping the path to expose before executing the handler
* @param uriTemplateVariables the URI template variables, can be {@code null} if no variables found
* @return the final handler object
*/
protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
String pathWithinMapping, @Nullable Map<String, String> uriTemplateVariables) {
//构建一个处理器执行器链
HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
//添加一个默认的拦截器,这个拦截器用来将解析好的请求路径保存到请求域中,见9
chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
//添加一个默认的拦截器,用来处理模板变量
if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
}
return chain;
}
4.1.1.2 获取路径匹配器
//路径匹配器,实现为Ant样式路径匹配
private PathMatcher pathMatcher = new AntPathMatcher();
/**
* Return the PathMatcher implementation to use for matching URL paths
* against registered URL patterns.
*/
public PathMatcher getPathMatcher() {
return this.pathMatcher;
}
4.1.1.3 是否开启尾部斜杠匹配
//默认为false,不开启尾部斜杠匹配
private boolean useTrailingSlashMatch = false;
/**
* Whether to match to URLs irrespective of the presence of a trailing slash.
*/
public boolean useTrailingSlashMatch() {
return this.useTrailingSlashMatch;
}
4.1.2 获取根路径的处理器
//默认是null,需要用户指定
@Nullable
private Object rootHandler;
/**
* Return the root handler for this handler mapping (registered for "/"),
* or {@code null} if none.
*/
@Nullable
public Object getRootHandler() {
return this.rootHandler;
}
默认是不存在的,需要用户指定
处理器需要实现
HttpHandleRequest
接口
4.1.3 获取默认的处理器
//默认是null,需要用户指定
@Nullable
private Object defaultHandler;
/**
* Return the default handler for this handler mapping,
* or {@code null} if none.
*/
@Nullable
public Object getDefaultHandler() {
return this.defaultHandler;
}
默认是不存在的,需要用户指定
处理器需要实现
HttpHandleRequest
接口
4.2 向处理器执行器链中添加拦截器
//保存着所有的拦截器对象
private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>();
/**
* Build a {@link HandlerExecutionChain} for the given handler, including
* applicable interceptors.
* <p>The default implementation builds a standard {@link HandlerExecutionChain}
* with the given handler, the common interceptors of the handler mapping, and any
* {@link MappedInterceptor MappedInterceptors} matching to the current request URL. Interceptors
* are added in the order they were registered. Subclasses may override this
* in order to extend/rearrange the list of interceptors.
* <p><b>NOTE:</b> The passed-in handler object may be a raw handler or a
* pre-built {@link HandlerExecutionChain}. This method should handle those
* two cases explicitly, either building a new {@link HandlerExecutionChain}
* or extending the existing chain.
* <p>For simply adding an interceptor in a custom subclass, consider calling
* {@code super.getHandlerExecutionChain(handler, request)} and invoking
* {@link HandlerExecutionChain#addInterceptor} on the returned chain object.
* @param handler the resolved handler instance (never {@code null})
* @param request current HTTP request
* @return the HandlerExecutionChain (never {@code null})
* @see #getAdaptedInterceptors()
*/
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
//如果已经是处理器执行器链了就直接使用,否则将处理器构造为处理器执行器链
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
/**
* 从请求域中拿到LOOKUP_PATH对应值,
* 如果拿不到,就使用UrlPathHelper重新解析路径
*/
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
//遍历所有的拦截器
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
/**
* 设置了拦截路径的拦截器,会被适配为MappedInterceptor
* 这个MappedInterceptor中有matches()方法用来判断拦截路径和请求路径是否匹配
*/
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
//拦截所有
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
对于
SimpleUrlHandlerMapping
来说,此处不存在构建处理器执行器链(4.1
中已经构建了),此方法仅将匹配的拦截器封装到处理器执行器链中
5 ResourceHttpRequestHandler
先来看一下它的类图
- 核心接口是
HttpRequestHandler
,接口唯一方法handleRequest()完成对静态资源请求的处理
InitializingBean
接口完成这个处理器的初始化工作,我们需要重点关注一下
先来看一下这个类的字段
public class ResourceHttpRequestHandler extends WebContentGenerator
implements HttpRequestHandler, EmbeddedValueResolverAware, InitializingBean, CorsConfigurationSource {
private static final String URL_RESOURCE_CHARSET_PREFIX = "[charset=";
private final List<String> locationValues = new ArrayList<>(4);
/**
* 静态资源所在的位置
* 我们配置的classpath:/static/js、classpath:/static/css最终会被解析为
* Resource对象,代表该文件夹下的所有资源
*/
private final List<Resource> locations = new ArrayList<>(4);
//资源、编码之间的映射
private final Map<Resource, Charset> locationCharsets = new HashMap<>(4);
//资源解析器
private final List<ResourceResolver> resourceResolvers = new ArrayList<>(4);
//资源转换器
private final List<ResourceTransformer> resourceTransformers = new ArrayList<>(4);
//资源解析器链,见7
@Nullable
private ResourceResolverChain resolverChain;
//资源转换器链,见8
@Nullable
private ResourceTransformerChain transformerChain;
//http资源消息转换器,将资源写入到响应体中,所有资源
@Nullable
private ResourceHttpMessageConverter resourceHttpMessageConverter;
//http资源消息转换器,将资源写入到响应体中,部分资源,断点下载
@Nullable
private ResourceRegionHttpMessageConverter resourceRegionHttpMessageConverter;
//内容协商管理器,属性填充阶段自动容器中填充进来
@Nullable
private ContentNegotiationManager contentNegotiationManager;
//文件扩展名->媒体类型映射
private final Map<String, MediaType> mediaTypes = new HashMap<>(4);
@Nullable
private CorsConfiguration corsConfiguration;
//路径处理帮助器,属性填充阶段自动填充一个
@Nullable
private UrlPathHelper urlPathHelper;
//值解析器
@Nullable
private StringValueResolver embeddedValueResolver;
}
5.1 afterPropertiesSet()
方法,处理器初始化
@Override
public void afterPropertiesSet() throws Exception {
resolveResourceLocations();
if (logger.isWarnEnabled() && CollectionUtils.isEmpty(this.locations)) {
logger.warn("Locations list is empty. No resources will be served unless a " +
"custom ResourceResolver is configured as an alternative to PathResourceResolver.");
}
//初始化资源解析器
if (this.resourceResolvers.isEmpty()) {
this.resourceResolvers.add(new PathResourceResolver());
}
//初始化资源解析器允许解析的资源,见5.1.1
initAllowedLocations();
// Initialize immutable resolver and transformer chains
//初始化一个资源解析器链,见7
this.resolverChain = new DefaultResourceResolverChain(this.resourceResolvers);
//初始化一个资源转换器链,见8
this.transformerChain = new DefaultResourceTransformerChain(this.resolverChain, this.resourceTransformers);
//两个http资源消息转换器,负责将静态资源写入响应中
if (this.resourceHttpMessageConverter == null) {
this.resourceHttpMessageConverter = new ResourceHttpMessageConverter();
}
if (this.resourceRegionHttpMessageConverter == null) {
this.resourceRegionHttpMessageConverter = new ResourceRegionHttpMessageConverter();
}
//获取内容协商管理器,见5.1.2
ContentNegotiationManager manager = getContentNegotiationManager();
//将内容协商管理器中注册媒体类型映射注册到当前处理器中,见5.1.3
if (manager != null) {
setMediaTypes(manager.getMediaTypeMappings());
}
//空方法,直接忽略
@SuppressWarnings("deprecation")
org.springframework.web.accept.PathExtensionContentNegotiationStrategy strategy =
initContentNegotiationStrategy();
if (strategy != null) {
setMediaTypes(strategy.getMediaTypes());
}
}
总结一下该方法做的事情
- 初始化一个默认的资源解析器
PathResourceResolver
,如果用户未指定的话- 设置资源解析器解析资源的位置
- 根据目前拥有的资源解析器构造一个资源解析器链
- 根据目前拥有的资源转换器构造一个资源转换器链
- 初始化两个
http资源消息转换器
ResourceHttpMessageConverter
::写入完整资源ResourceRegionHttpMessageConverter
:写入部分资源- 将内容协商管理器中资源类型->注册媒体类型映射注册到当前处理器中
5.1.1 初始化资源解析器允许解析的资源
/**
* Look for a {@code PathResourceResolver} among the configured resource
* resolvers and set its {@code allowedLocations} property (if empty) to
* match the {@link #setLocations locations} configured on this class.
*/
protected void initAllowedLocations() {
//用户未在<mvc:resource >中配置静态资源的位置
if (CollectionUtils.isEmpty(this.locations)) {
return;
}
//遍历所有的资源解析器
for (int i = getResourceResolvers().size() - 1; i >= 0; i--) {
if (getResourceResolvers().get(i) instanceof PathResourceResolver) {
PathResourceResolver pathResolver = (PathResourceResolver) getResourceResolvers().get(i);
//设置资源解析器允许解析的资源
if (ObjectUtils.isEmpty(pathResolver.getAllowedLocations())) {
pathResolver.setAllowedLocations(getLocations().toArray(new Resource[0]));
}
if (this.urlPathHelper != null) {
//设置资源编码
pathResolver.setLocationCharsets(this.locationCharsets);
//设置路径处理帮助器
pathResolver.setUrlPathHelper(this.urlPathHelper);
}
break;
}
}
}
可以看到,这个方法将静态资源所在位置设置到了资源解析器中。这样,有对应静态资源请求过来之后,就会调用这个资源解析器去指定位置加载静态资源
5.1.2 获取内容协商管理器
/**
* Return the configured content negotiation manager.
* @since 4.3
* @deprecated as of 5.2.4.
*/
@Nullable
@Deprecated
public ContentNegotiationManager getContentNegotiationManager() {
return this.contentNegotiationManager;
}
5.1.3 将文件扩展名->媒体类型映射注册到处理器中
/**
* Add mappings between file extensions, extracted from the filename of a
* static {@link Resource}, and corresponding media type to set on the
* response.
* <p>Use of this method is typically not necessary since mappings are
* otherwise determined via
* {@link javax.servlet.ServletContext#getMimeType(String)} or via
* {@link MediaTypeFactory#getMediaType(Resource)}.
* @param mediaTypes media type mappings
* @since 5.2.4
*/
public void setMediaTypes(Map<String, MediaType> mediaTypes) {
mediaTypes.forEach((ext, mediaType) ->
this.mediaTypes.put(ext.toLowerCase(Locale.ENGLISH), mediaType));
}
很少用,注释上面也说了,一般使用
ServletContext
的getMimeType(String)
方法或者MediaTypeFactory
的getMediaType(Resource)
来得到资源的媒体类型,见5.2.4
5.2 handleRequest(HttpServletRequest request, HttpServletResponse response)
方法,完成静态资源请求处理
/**
* Processes a resource request.
* <p>Checks for the existence of the requested resource in the configured list of locations.
* If the resource does not exist, a {@code 404} response will be returned to the client.
* If the resource exists, the request will be checked for the presence of the
* {@code Last-Modified} header, and its value will be compared against the last-modified
* timestamp of the given resource, returning a {@code 304} status code if the
* {@code Last-Modified} value is greater. If the resource is newer than the
* {@code Last-Modified} value, or the header is not present, the content resource
* of the resource will be written to the response with caching headers
* set to expire one year in the future.
*/
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// For very general mappings (e.g. "/") we need to check 404 first
//检查能否根据路径加载到静态资源,见5.2.1
Resource resource = getResource(request);
if (resource == null) {
logger.debug("Resource not found");
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
//OPTIONS类型请求设置允许访问后直接跳过处理
if (HttpMethod.OPTIONS.matches(request.getMethod())) {
response.setHeader("Allow", getAllowHeader());
return;
}
// Supported methods and required session
//检查是否支持处理本次请求,见5.2.2
checkRequest(request);
// Header phase
if (new ServletWebRequest(request, response).checkNotModified(resource.lastModified())) {
logger.trace("Resource not modified");
return;
}
// Apply cache settings, if any
//设置静态资源的缓存时间,见5.2.3
prepareResponse(response);
// Check the media type for the resource
//获取资源对应的媒体类型,见5.2.4
MediaType mediaType = getMediaType(request, resource);
//设置响应资源的内容类型和大小,见5.2.5
setHeaders(response, resource, mediaType);
// Content phase
ServletServerHttpResponse outputMessage = new ServletServerHttpResponse(response);
//未设置Range请求头表示需要全部的静态资源
if (request.getHeader(HttpHeaders.RANGE) == null) {
Assert.state(this.resourceHttpMessageConverter != null, "Not initialized");
//将资源写入过程委派给了ResourceHttpMessageConverter完成
this.resourceHttpMessageConverter.write(resource, mediaType, outputMessage);
}
/**
* 设置了Range请求头表示只需要部分资源,即断点下载功能
* Range请求头中会指定需要资源的开始和结束的索引位置
* 此处只需要Range请求头中指定索引位置的静态资源
*/
else {
Assert.state(this.resourceRegionHttpMessageConverter != null, "Not initialized");
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(request);
try {
//得到Range请求头的内容
List<HttpRange> httpRanges = inputMessage.getHeaders().getRange();
//设置响应状态码为206
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
//只写入指定位置和大小的静态资源
this.resourceRegionHttpMessageConverter.write(
HttpRange.toResourceRegions(httpRanges, resource), mediaType, outputMessage);
}
catch (IllegalArgumentException ex) {
//"Content-Range"
response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes */" + resource.contentLength());
//设置错误状态码416
response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
}
}
}
通过执行
DefaultServletHttpRequestHandler
的handleRequest()
方法,请求就被转发到了Tomcat
的默认Servlet
处理
5.2.1 根据路径加载静态资源
@Nullable
protected Resource getResource(HttpServletRequest request) throws IOException {
/**
* org.springframework.web.servlet.HandlerMapping.pathWithinHandlerMapping
* 得到保存在请求域属性中的路径,这个路径是在PathExposingHandlerInterceptor中设置
* 进去的,见9.1
*/
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);
/**
* isInvalidPath()方法,从3个方面检查请求路径是不是一个无效的路径
* 1.路径中包含"WEB-INF"或者 "META-INF"不是一个无效路径
* 2."../"这种的路径也不是无效路径
* 3.ResourceUtils类isUrl()方法判断是不是一个无效路径
* 经过这3个检验通过之后,就是一个无效路径了
*/
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.");
/**
* 根据路径加载资源
* getLocations()方法,获取统一资源描述,这里是ClassPathResource
*/
Resource resource = this.resolverChain.resolveResource(request, path, getLocations());
if (resource != null) {
resource = this.transformerChain.transform(request, resource);
}
return resource;
}
//返回静态资源所在位置的统一资源描述
public List<Resource> getLocations() {
return this.locations;
}
方法流程如下所示:
- 替换路径中使用不正确的斜杠
- 替换完成后检查路径是否有效
- 路径中是否使用了无效的转义字符
- 经过上述
3
步验证之后,资源解析器根据请求路径解析加载对应位置下的资源
5.2.2 检查处理器是否支持处理该请求
/**
* 支持处理的请求方式
* 默认为GET,HEAD两种
*/
@Nullable
private Set<String> supportedMethods;
//是否需要验证session
private boolean requireSession = false;
/**
* Check the given request for supported methods and a required session, if any.
* @param request current HTTP request
* @throws ServletException if the request cannot be handled because a check failed
* @since 4.2
*/
protected final void checkRequest(HttpServletRequest request) throws ServletException {
// Check whether we should support the request method.
//获取本次请求的请求方式
String method = request.getMethod();
//不支持处理就抛出异常
if (this.supportedMethods != null && !this.supportedMethods.contains(method)) {
throw new HttpRequestMethodNotSupportedException(method, this.supportedMethods);
}
// Check whether a session is required.
if (this.requireSession && request.getSession(false) == null) {
throw new HttpSessionRequiredException("Pre-existing session required but none found");
}
}
该方法从两个方面检查处理器是否支持处理该请求
- 请求方式,仅限
GET
、HEAD
session
是否必须
5.2.3 设置静态资源的缓存时间
//这两个属性是WebContentGenerator的
/**
* 缓存控制器,默认为null,一般都是使用cacheSeconds
* 控制静态资源的缓存时间,
* 会将缓存控制器的配置写入响应头中
*/
@Nullable
private CacheControl cacheControl;
//缓存秒数,可通过<mvc:resource />标签修改
private int cacheSeconds = -1;
/**
* Prepare the given response according to the settings of this generator.
* Applies the number of cache seconds specified for this generator.
* @param response current HTTP response
* @since 4.2
*/
protected final void prepareResponse(HttpServletResponse response) {
//缓存控制器的配置写入响应头
if (this.cacheControl != null) {
applyCacheControl(response, this.cacheControl);
}
//缓存秒数写入响应头
else {
applyCacheSeconds(response, this.cacheSeconds);
}
if (this.varyByRequestHeaders != null) {
for (String value : getVaryRequestHeadersToAdd(response, this.varyByRequestHeaders)) {
response.addHeader("Vary", value);
}
}
}
在此处完成静态资源的缓存时间的控制
5.2.4 获取资源对应的媒体类型
/**
* 默认支持两种类型,见5.1.3
* json->application/json
* xml->application/xml
* 这两种是内容协商管理器中自带的
*/
private final Map<String, MediaType> mediaTypes = new HashMap<>(4);
/**
* Determine the media type for the given request and the resource matched
* to it. This implementation tries to determine the MediaType using one of
* the following lookups based on the resource filename and its path
* extension:
* <ol>
* <li>{@link javax.servlet.ServletContext#getMimeType(String)}
* <li>{@link #getMediaTypes()}
* <li>{@link MediaTypeFactory#getMediaType(String)}
* </ol>
* @param request the current request
* @param resource the resource to check
* @return the corresponding media type, or {@code null} if none found
*/
@Nullable
protected MediaType getMediaType(HttpServletRequest request, Resource resource) {
MediaType result = null;
/**
* mimeType = application/javascript
* resource.getFilename()方法, 获取当前请求资源的名字
* 该方法会得到它要响应的资源的内容类型
*/
String mimeType = request.getServletContext().getMimeType(resource.getFilename());
if (StringUtils.hasText(mimeType)) {
//转化为媒体类型
result = MediaType.parseMediaType(mimeType);
}
/**
* 如果Servlet上下文不能识别资源的内容类型
* 那么就在此处进一步处理,根据文件的扩展名来获取最终响应的媒体类型
*/
if (result == null || MediaType.APPLICATION_OCTET_STREAM.equals(result)) {
MediaType mediaType = null;
String filename = resource.getFilename();
//文件扩展名
String ext = StringUtils.getFilenameExtension(filename);
if (ext != null) {
//判断是不是xml或json
mediaType = this.mediaTypes.get(ext.toLowerCase(Locale.ENGLISH));
}
//通过媒体类型工厂获取后缀名对应的媒体类型
if (mediaType == null) {
mediaType = MediaTypeFactory.getMediaType(filename).orElse(null);
}
if (mediaType != null) {
result = mediaType;
}
}
return result;
}
总结:
- 先使用
ServletContext
的getMimeType(资源名)
方法,直接获取资源的内容类型- 如果获取不到,那么尝试使用
MediaTypeFactory
获取资源后缀名对应的媒体类型MediaTypeFactory
中存储了所有可能会用到的资源类型->媒体类型
的映射关系
5.2.5 设置响应资源的内容类型和大小
/**
* Set headers on the given servlet response.
* Called for GET requests as well as HEAD requests.
* @param response current servlet response
* @param resource the identified resource (never {@code null})
* @param mediaType the resource's media type (never {@code null})
* @throws IOException in case of errors while setting the headers
*/
protected void setHeaders(HttpServletResponse response, Resource resource, @Nullable MediaType mediaType)
throws IOException {
//设置资源的大小
long length = resource.contentLength();
if (length > Integer.MAX_VALUE) {
response.setContentLengthLong(length);
}
else {
response.setContentLength((int) length);
}
//设置响应的内容类型
if (mediaType != null) {
response.setContentType(mediaType.toString());
}
/**
* HttpResource是Resource的扩展接口
* 代表一个需要写入响应的资源,接口只有一个方法getResponseHeaders()
* 代表为该资源设置的响应头
*/
if (resource instanceof HttpResource) {
HttpHeaders resourceHeaders = ((HttpResource) resource).getResponseHeaders();
resourceHeaders.forEach((headerName, headerValues) -> {
boolean first = true;
for (String headerValue : headerValues) {
if (first) {
response.setHeader(headerName, headerValue);
}
else {
response.addHeader(headerName, headerValue);
}
first = false;
}
});
}
/**
* "Accept-Ranges"
* 代表了该服务器可以接受范围请求,即我们常见的断点下载功能
* 可以通过请求头Range来定义获取的字节范围,格式为 bytes=0-8,那么只会获取这个范围的字节数据
*/
response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
}
该方法只是简单的将资源类型和资源大小设置在响应头中,并允许断点下载
6 HttpRequestHandlerAdapter
与
SimpleUrlHandlerMapping
对应
public class HttpRequestHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
//只要处理器是HttpRequestHandler类型的,就使用该处理器适配器
return (handler instanceof HttpRequestHandler);
}
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//处理过程就是调用HttpRequestHandler的handleRequest()方法,并返回一个null
((HttpRequestHandler) handler).handleRequest(request, response);
return null;
}
@Override
public long getLastModified(HttpServletRequest request, Object handler) {
if (handler instanceof LastModified) {
return ((LastModified) handler).getLastModified(request);
}
return -1L;
}
}
该处理器适配器,将所有的处理过程交给了
HttpRequestHandler
来处理,处理过程见5
7 DefaultResourceResolverChain
该类只有一个唯一的接口
ResourceResolverChain
public interface ResourceResolverChain {
/**
* 解析路径对应的资源
*/
@Nullable
Resource resolveResource(
@Nullable HttpServletRequest request, String requestPath, List<? extends Resource> locations);
/**
* 解析请求路径
*/
@Nullable
String resolveUrlPath(String resourcePath, List<? extends Resource> locations);
}
下面是该类的完整实现
class DefaultResourceResolverChain implements ResourceResolverChain {
//当前使用的资源解析器
@Nullable
private final ResourceResolver resolver;
//下一个资源解析器所在位置
@Nullable
private final ResourceResolverChain nextChain;
//构造方法
public DefaultResourceResolverChain(@Nullable List<? extends ResourceResolver> resolvers) {
resolvers = (resolvers != null ? resolvers : Collections.emptyList());
DefaultResourceResolverChain chain = initChain(new ArrayList<>(resolvers));
this.resolver = chain.resolver;
this.nextChain = chain.nextChain;
}
//初始化链
private static DefaultResourceResolverChain initChain(ArrayList<? extends ResourceResolver> resolvers) {
//创建一个空的链
DefaultResourceResolverChain chain = new DefaultResourceResolverChain(null, null);
//得到这个list集合迭代器
ListIterator<? extends ResourceResolver> it = resolvers.listIterator(resolvers.size());
//反向遍历这个迭代器,将这些资源解析器构造为一个链条,典型的责任链模式
while (it.hasPrevious()) {
chain = new DefaultResourceResolverChain(it.previous(), chain);
}
return chain;
}
//构造方法
private DefaultResourceResolverChain(@Nullable ResourceResolver resolver, @Nullable ResourceResolverChain chain) {
Assert.isTrue((resolver == null && chain == null) || (resolver != null && chain != null),
"Both resolver and resolver chain must be null, or neither is");
this.resolver = resolver;
this.nextChain = chain;
}
/******************************内部最终调用它持有的资源解析*************************/
//接口方法,解析请求路径对应的资源
@Override
@Nullable
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);
}
//接口方法,解析请求路径
@Override
@Nullable
public String resolveUrlPath(String resourcePath, List<? extends Resource> locations) {
return (this.resolver != null && this.nextChain != null ?
this.resolver.resolveUrlPath(resourcePath, locations, this.nextChain) : null);
}
}
典型的责任链模式,它将一系列的资源解析器构造为一个链条,每一个节点除了持有一个资源解析器,还持有一个指向下一个节点的引用。
8 DefaultResourceTransformerChain
该类只有一个唯一的接口
ResourceTransformerChain
public interface ResourceTransformerChain {
/**
* 获取资源解析器链
*/
ResourceResolverChain getResolverChain();
/**
* 转换给定的资源
*/
Resource transform(HttpServletRequest request, Resource resource) throws IOException;
}
下面是该类的完整实现
class DefaultResourceTransformerChain implements ResourceTransformerChain {
//资源解析器链
private final ResourceResolverChain resolverChain;
//当前使用的资源转换器
@Nullable
private final ResourceTransformer transformer;
//下一个资源转换器的应用
@Nullable
private final ResourceTransformerChain nextChain;
//构造方法
public DefaultResourceTransformerChain(
ResourceResolverChain resolverChain, @Nullable List<ResourceTransformer> transformers) {
Assert.notNull(resolverChain, "ResourceResolverChain is required");
this.resolverChain = resolverChain;
transformers = (transformers != null ? transformers : Collections.emptyList());
//将资源转换器初始化为一个链条
DefaultResourceTransformerChain chain = initTransformerChain(resolverChain, new ArrayList<>(transformers));
this.transformer = chain.transformer;
this.nextChain = chain.nextChain;
}
/**
* 资源转换器初始化为一个链条
* 其过程和初始化资源解析器链条差不多,都是反向遍历,然后得到一个正向的链条
*/
private DefaultResourceTransformerChain initTransformerChain(ResourceResolverChain resolverChain,
ArrayList<ResourceTransformer> transformers) {
DefaultResourceTransformerChain chain = new DefaultResourceTransformerChain(resolverChain, null, null);
ListIterator<? extends ResourceTransformer> it = transformers.listIterator(transformers.size());
while (it.hasPrevious()) {
chain = new DefaultResourceTransformerChain(resolverChain, it.previous(), chain);
}
return chain;
}
//构造方法
public DefaultResourceTransformerChain(ResourceResolverChain resolverChain,
@Nullable ResourceTransformer transformer, @Nullable ResourceTransformerChain chain) {
Assert.isTrue((transformer == null && chain == null) || (transformer != null && chain != null),
"Both transformer and transformer chain must be null, or neither is");
this.resolverChain = resolverChain;
this.transformer = transformer;
this.nextChain = chain;
}
/***********************************接口方法*********************************/
@Override
public ResourceResolverChain getResolverChain() {
return this.resolverChain;
}
//借助资源转换器进行转换
@Override
public Resource transform(HttpServletRequest request, Resource resource) throws IOException {
return (this.transformer != null && this.nextChain != null ?
this.transformer.transform(request, resource, this.nextChain) : resource);
}
}
典型的责任链模式,它将一系列的资源转换器构造为一个链条,每一个节点除了持有一个资源转换器,还持有一个指向下一个节点的引用。类似于
Filter
9 PathExposingHandlerInterceptor
先来看一下它的类图
它就是一个拦截器,我们直接看源码,看看这个拦截器的作用
该类是
AbstractUrlHandlerMapping
类的内部类
/**
* Special interceptor for exposing the
* {@link AbstractUrlHandlerMapping#PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE} attribute.
* @see AbstractUrlHandlerMapping#exposePathWithinMapping
*/
private class PathExposingHandlerInterceptor extends HandlerInterceptorAdapter {
/**
* 对于静态资源请求来说,它就是资源匹配的路径
* 例如<mvc:resources mapping="/js/**">
* <mvc:resources mapping="/css/**">
* 如果请求是以js开头的,那么它最匹配的路径就是"/js/**",也就是该字段的值
*/
private final String bestMatchingPattern;
/**
* 对于静态资源请求来说,它就是截取掉请求路径中匹配的路径之后,剩下的路径
* 比如js/**和js/test.js,那么它就是test.js
*/
private final String pathWithinMapping;
//构造方法,实例化对象的位置在4.1.1
public PathExposingHandlerInterceptor(String bestMatchingPattern, String pathWithinMapping) {
this.bestMatchingPattern = bestMatchingPattern;
this.pathWithinMapping = pathWithinMapping;
}
//处理器执行之前调用
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
//将解析好的路径保存到请求域中,见9.1
exposePathWithinMapping(this.bestMatchingPattern, this.pathWithinMapping, request);
//将处理器对象也保存到请求域中
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, handler);
//将是否支持类级别映射的结果也保存到请求域中
request.setAttribute(INTROSPECT_TYPE_LEVEL_MAPPING, supportsTypeLevelMappings());
return true;
}
}
这个拦截器的主要作用就是将解析好的路径保存到请求域中,方便处理器使用
9.1 exposePathWithinMapping(String bestMatchingPattern, String pathWithinMapping,HttpServletRequest request)
方法,将解析好的路径保存到请求域中
该方法在它的外部类AbstractUrlHandlerMapping
中
/**
* Expose the path within the current mapping as request attribute.
* @param pathWithinMapping the path within the current mapping
* @param request the request to expose the path to
* @see #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE
*/
protected void exposePathWithinMapping(String bestMatchingPattern, String pathWithinMapping,
HttpServletRequest request) {
//最匹配的资源路径
request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestMatchingPattern);
//这个就是5.2.1中要用到的路径,非常重要
request.setAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, pathWithinMapping);
}
该方法将和资源有关的两个路径都保存到请求域中,方便后续使用
9.2 supportsTypeLevelMappings()
方法, 是否支持类级别映射
/**
* Indicates whether this handler mapping support type-level mappings. Default to {@code false}.
*/
protected boolean supportsTypeLevelMappings() {
return false;
}
默认是不支持类级别映射的