1:简单介绍
该组件作用是根据请求信息获取对应的处理请求的handler,职责很单一,只干这一件事。该组件对应的接口是org.springframework.web.servlet.HandlerMapping
,只有唯一的一个用来根据请求获取handler的方法getHandler(req)
,源码如下:
public interface HandlerMapping {
String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
@Nullable
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
其它的还有一些供子类使用的常量,比如最匹配handler常量BEST_MATCHING_HANDLER_ATTRIBUTE
,按照一般的编程套路,会给该接口提供一个默认的抽象实现类,提供骨架逻辑来获取处理器+拦截器数组
,然后暴露一个模板方法
供子类实现,确实是如此,而这个抽象骨架类就是org.springframework.web.servlet.handler.AbstractHanlderMapping
,这个模板方法是handleInernal(req)
,该方法是一个抽象方法,强制子类实现,定义如下:
@Nullable
protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;
好的,下面我们就从这个骨架抽象类
开始吧!
2:AbstractHandlerMapping
2.1:类定义
- 源码
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
implements HandlerMapping, Ordered, BeanNameAware {}
- 继承WebApplicationObjectSupport
首先继承了org.springframework.web.context.support.WebApplicationObjectSupport
,而该类定义如下:
public abstract class WebApplicationObjectSupport extends ApplicationObjectSupport implements ServletContextAware {}
因为实现了org.spingframework.web.context.ServletContextAware
因此会通过方法setServletContext(sc)
来索要javax.servlet.ServletContext
,然后再看ApplicationObjectSupport
,其定义如下:
public abstract class ApplicationObjectSupport implements ApplicationContextAware {}
那么是通过setApplicationContext(sc)
来索要ApplicationContext对象吗?其实不是的,setApplicationContext方法是在ApplicationObjectSupport方法中定义,但是该方法会调用模板方法initApplicationContext
,而AbstractHandlerMapping正式通过重写该模板方法,从而实现加入spring的启动过程,从而实现handler interceptor的初始化工作的
,那么在AbstractHandlerMapping中应该有setServletContext(sc)
和initWebApplicationContext
两个方法分别来索要代表web容器的ServletContext
和加入spring加载过程的intiApplicationContext
,其实只有后一个方法,前一个方法是在WebApplicationObjectSupport中定义的,源码如下:
org.springframework.web.context.support.WebApplicationObjectSupport
@Override
public final void setServletContext(ServletContext servletContext) {
}
org.springframework.web.servlet.handler.AbstractHandlerMapping
@Override
protected void initApplicationContext() throws BeansException {
}
这里的org.springframework.web.servlet.handler.AbstractHandlerMapping#initApplicationContext
方法也正是HandlerInterceptor初始化的入口
了。
- 实现Orderd接口
当有多个时可以指定顺序,值越小优先级越高。 - 实现BeanNameAware接口
通过方法setBeanName(name)索要
自己作为spring容器bean的名称,源码如下:
@Override
public void setBeanName(String name) {
this.beanName = name;
}
2.2:AbstractHandlerMapping#initApplicationContext
源码如下:
@Override
protected void initApplicationContext() throws BeansException {
extendInterceptors(this.interceptors);
detectMappedInterceptors(this.adaptedInterceptors);
initInterceptors();
}
方法extendInterceptors(this.interceptors)
是一个模板方法,目前并没有用到,可以忽略,方法detectMappedInterceptors(this.adptedInterceptors)
方法用于从容器中获取org.springframework.web.servlet.handler.MappedInterceptor
以及其祖先类型的bean集合,方法源码如下:
protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) {
mappedInterceptors.addAll(BeanFactoryUtils.beansOfTypeIncludingAncestors(
obtainApplicationContext(), MappedInterceptor.class, true, false).values());
}
直接使用BeanFactoryUtils
工具类来获取,如果我们定义了如下两个拦截器:
public class DemoHandlerInterceptor implements HandlerInterceptor {}
public class DemoHandlerInterceptor1 implements HandlerInterceptor {}
并通过如下方式注册:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/testinterceptor/hi"/>
<bean class="dongshi.handlerinterceptor.DemoHandlerInterceptor"></bean>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/testinterceptor/hi"/>
<bean class="dongshi.handlerinterceptor.DemoHandlerInterceptor1"/>
</mvc:interceptor>
</mvc:interceptors>
此时debug看的话,就能够看到我们定义的HandlerInterceptor
:
一共三个元素,其中第一个位置为springmvc自己的,可以忽略,第二个和第三个就是我们自己定义的拦截器。方法initInterceptors
方法是将private final List<Object> interceptors = new ArrayList<>();
中的拦截器转换为HandlerInterceptor然后添加到adaptedInterceptors
集合中,方法如下:
protected void initInterceptors() {
if (!this.interceptors.isEmpty()) {
for (int i = 0; i < this.interceptors.size(); i++) {
Object interceptor = this.interceptors.get(i);
if (interceptor == null) {
throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
}
this.adaptedInterceptors.add(adaptInterceptor(interceptor));
}
}
}
其中方法adtedInterceptor(interceptor)
方法是转换的方法,支持HanlderInteceptor
,WebReqeustInterceptor
两种类型,源码如下:
protected HandlerInterceptor adaptInterceptor(Object interceptor) {
if (interceptor instanceof HandlerInterceptor) {
return (HandlerInterceptor) interceptor;
}
else if (interceptor instanceof WebRequestInterceptor) {
return new WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
}
else {
throw new IllegalArgumentException("Interceptor type not supported: " + interceptor.getClass().getName());
}
}
到这里HandlerExecutionChain中的所需要的HandlerInterceptor已经全部准备好了,其实在initApplicationContext也就做了这么多工作,这个过程是在启动的过程中完成的,剩下的一部分就是Handler的获取了,这部分内容的获取是在请求过程中动态执行的,调用的方法是org.springframework.web.servlet.handler.AbstractHandlerMapping#getHandler
。
2.3:AbstractHandlerMapping#getHandler
当请求到达,最终会通过org.springframework.web.servlet.DispatcherServlet#doDispatch
处理,具体可以看一次GET请求在springmvc中是的处理流程
,好的,入口我们也就找到了,源码如下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
...
mappedHandler = getHandler(processedRequest);
...
}
其中getHandler
方法会循环HandlerMapping实现类获取handler,代码如下:
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;
}
这里我们测试的接口如下:
@Controller
@RequestMapping("/testinterceptor")
public class TestinterceptorController {
@RequestMapping("/hi")
@ResponseBody
public String hi() {
String msg = "testinterceptor hi";
System.out.println(msg);
return msg;
}
}
因此最终会使用RequestMappingHandlerMapping
来获取这个@RequestMapping("/hi")的handler
。访问接口/testinterceptor/hi
debug查看:
说明一下这个getHandlerInternal
是一个模板方法,调用的是RequestMappingHandlerMapping
的具体实现,这里先不详细看内部细节,接下来是通过方法getHandlerExecutionChain
方法获取handler相关的HandlerInterceptor,源码如下:
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
// <1>
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
// <2>
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
<1>
处代码获取请求的路径,<2>
处代码遍历所有的adaptedInterceptors
如果是org.springframework.web.servlet.handler.MappedInterceptor
类型,则判断拦截路径匹配才添加到chain中,否则直接添加,比如我们自定义的如下的两个HandlerInterceptor:
public class DemoHandlerInterceptor implements HandlerInterceptor {}
public class DemoHandlerInterceptor1 implements HandlerInterceptor {}
这里只看定义虽然和MappedInterceptor没关系,但是实际是会通过MappedInterptor进行封装,如下debug:
我们自定义的DemoHandlerInterceptor
被封装为了MappedInterceptor的一个属性而已。因此代码interceptor instanceof MappedInterceptor
为true。最终自定义的两个handler都会被添加到chain中,debug如下:
到这就获取到了请求对应的org.springframework.web.servlet.HandlerExecutionChain
对象了。
3:MatchableHanlderMapping
AbstractHandlerMapping只是HandlerMapping的其中一个子类,其类图如下图:
可以看到其另一个子接口MatchableHandlerMapping,其接口定义如下:
public interface MatchableHandlerMapping extends HandlerMapping {
@Nullable
RequestMatchResult match(HttpServletRequest request, String pattern);
}
只有一个方法match(req, res)
,提供了具体实现的子类有RequestMappingHandlerMapping
和AbstractUrlHandlerMapping
添加这两个类后uml图如下:
4:DispatcherServlet的handlerMappings
的初始化
DispatcherServlet的org.springframework.web.servlet.DispatcherServlet#handlerMappings
是维护了所有的HandlerMapping的集合,会在请求到达时用来获取对应的handler。该变量的初始化是通过调用DispatcherServlet的org.springframework.web.servlet.DispatcherServlet#initHandlerMappings
方法完成的,其实就是web容器按照servlet规范的要求来加载org.springframework.web.servlet.DispatcherServlet
的时候来调用的,源码如下:
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
// <1>
if (this.detectAllHandlerMappings) {
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values()); AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
// <2>
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
}
}
// <3>
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
<1>
处代码判断detectAllHandlerMappings
如果是为true(该值默认为true,一般也不需要修改,如果有特殊需要修改,可以通过配置进行设置),则获取容器中HandlerMapping包括了其祖先类型的bean,debug查看变量Map<String, HandlerMapping> matchingBeans
的结果如下:
获取后则设置到全局变量this.handlerMappings
。<2>
处代码是直接从容器中获取bean名称为HANDLER_MAPPING_BEAN_NAME=handlerMapping
的handlermapping。正常<3>
处代码不会进入,我们可以通过在web.xml中设置检测开关为false,然后,容器中不要有名称为handlerMapping
的HandlerMapping的bean,就可以了,设置如下:
<servlet>
<servlet-name>letsGO</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/letsGO-servlet.xml</param-value>
</init-param>
<init-param>
<param-name>detectAllHandlerMappings</param-name>
<param-value>false</param-value>
</init-param>
</servlet>
代码getDefaultStrategies(context, clz)
源码如下:
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
// <1>
String key = strategyInterface.getName();
String value = defaultStrategies.getProperty(key);
if (value != null) {
// <2>
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
// <3>
List<T> strategies = new ArrayList<>(classNames.length);
// <4>
for (String className : classNames) {
try {
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
// <5>
Object strategy = createDefaultStrategy(context, clazz);
strategies.add((T) strategy);
}
catch (ClassNotFoundException ex) {
...
}
catch (LinkageError err) {
...
}
}
return strategies;
}
else {
return new LinkedList<>();
}
}
先看下变量defaultStrategies
其初始化源码如下:
private static final Properties defaultStrategies;
static {
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
}
}
直接加载和DispatcherServlet在相同路径的DEFAULT_STRATEGIES_PATH=DispatcherServlet.properties
的文件的内容,该文件配置的都是一些默认的组件类,和此处相关的配置内容为:
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
将文件中键值对加载到变量defaultStrategies
中。<1>
处获取接口的名称其实就是配置文件中HandlerMapping的key,然后获取对应的值,debug看的话如下:
<2>
处代码为将逗号分割字符串格式数据分割为String的数组,<3>
处代码为最终的结果集合,<4>
为循环创建Class实例对象,最终结果debug如下:
<5>
处代码createDefaultStrategy(context, clz)
是通过spring ioc容器创建对象,代码如下:
protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) {
return context.getAutowireCapableBeanFactory().createBean(clazz);
}