概述
Spring MVC Web
应用开发时,我们会开发出相应的 controller class/method
, 关联到相应的url
,然后用户在访问系统时,输入正确的url
,相应的controller class/method
就会被执行,返回给用户相应的处理结果。那么Spring MVC Web
应用内部是如何找到一个请求对应的controller class/method
方法呢 ? 本文基于源代码回答此问题。
简单地讲,应用在启动的时候,Spring MVC
将配置文件或者通过注解@Controller+@RequestMapping
定义的<url,controller class/method>
映射关系搜集起来,保存在内存中,然后在请求到达时,根据请求的url(实际上也会使用相应的HTTP method
)就能找到相应的 controller class/method
。下面我们分成这两步进行分析。
本文中的源代码基于Spring MVC 4。
初始化 handlerMappings
DispatcherServlet
有一个属性handlerMappings
,用于在内存中保持配置定义的<url,controller class/method>
映射关系。在DispatcherServlet
初始化的过程中,该属性会根据配置信息被填充正确的信息。
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
// detectAllHandlerMappings 表示是否要从上下文中检测所有的 handlerMappings,缺省值为 true
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
// 从 ApplicationContext 及其祖先级别上下文中找出所有类型为 HandlerMapping 的 bean ,
// 这些 bean 实在 bean 扫描阶段根据配置或者注解进入到 Spring IoC 容器的。
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
// 对搜集到的 handlerMappings 进行排序
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}
找到当前请求的handler
每个请求到达DispatcherServlet
被处理时都会使用其方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response)
。该方法调用getHandler()
来获取当前请求的handler
。
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 遍历所有的 handlerMappings , 找到第一个支持当前请求 request 的那一个并返回一个 HandlerExecutionChain
// 对象,注意这里是 HandlerExecutionChain 对象,而不是一个什么 handler 对象,
// 一个 HandlerExecutionChain 的内容是 1 handler + N handler interceptors , 包含了相应的 handler 对象
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
// 如果当前 HandlerMapping hm 支持当前 request, 那就直接使用它;
// 如果当前 HandlerMapping hm 不支持当前 request, 其 getHandler 方法会返回 null;
// getHandler 内部会获取 request 的 url 信息 , http method 信息,然后判断自己是否支持该 request,
// 具体的实现逻辑不同的HandlerMapping实现类会有不同。
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
// 如果没有所有配置的 handlerMappings 都不支持该请求,则返回 null,
// 此时DispatcherServlet会抛出一个异常NoHandlerFoundException或者向客户端返回一个404,
// 具体怎么做看开关的设置,缺省是会向客户端返回一个404。
return null;
}
这里需要注意的是 getHandler
方法的返回结果并不是目标handler
,而是包含了目标handler
的一个HandlerExecutionChain
对象。HandlerExecutionChain
可以被理解成是Spring
对目标handler
的封装。
一个HandlerExecutionChain
包含一个目标handler
,同时也包含了这个handler
的前置/后置/完成时拦截器(pre/post/complete HandlerInterceptor
)。