一、SpringMVC概述
1.1 SpringMVC请求处理流程
在阅读源码之前先进行SpringMVC源码环境的搭建SpringMVC环境搭建
里面也对Controller控制器的几种实现方式,进行了简单说明,便于我们后续的理解。
下面先给出一张流程图,方便我们进行梳理。
DispatcherServlet:DispatcherServlet是SpringMVC中的前端控制器,负责接收request并将request转发给对应的处理组件。
HandlerMapping:HanlerMapping是SpringMVC中完成url到Controller映射的组件。
Handler:Handler处理器,其实就是我们的Controller,Controller是SpringMVC中负责处理request的组件。
ModelAndView:ModelAndView是封装结果视图的组件。
ViewResolver:ViewResolver视图解析器,解析ModelAndView对象,并返回对应的视图View给客户端。
处理流程:
1.首先,请求进入DispatcherServlet,由DispatcherServlet从HandlerMapping中提取对应的Handler。
2.此时,只是获取到了对应的Handler,然后得去寻找对应的适配器,即:HandlerAdapter。
3.拿到对应的HandlerAdapter后,开始调用对应的Handler处理业务逻辑,执行完成之后返回一个ModelAndView。
4.这时候交给我们的ViewResolver,通过视图名称查找对应的视图,然后返回。
5.最后,渲染视图,返回渲染后的视图,响应给客户端。
1.2 SpringMVC工作机制
在容器初始化时会建立所有url和controller的对应关系,保存到Map<url,controller>中。
tomcat启动时会通知spring初始化容器(加载bean的定义信息和初始化所有单例bean),然后springmvc会遍历容器中的bean,获取每一个controller中的所有方法访问的url,然后将url和Controller保存到一个Map中;
这样就可以根据request快速定位到Controller,因为最终处理request的是Controller中的方法,Map中只保留了url和Controller中的对应关系,所以要根据request的url进一步确认Controller中的method。
这一步工作的原理就是拼接Controller的url(Controller上@RequestMapping的值)和方法的url(method上@RequestMapping的值),与request的url进行匹配,找到匹配的那个方法;
确定处理请求的method后,接下来的任务就是参数绑定,把request中参数绑定到方法的形式参数上,这一步是整个请求处理过程中最复杂的一个步骤。
SpringMVC提供了两种request参数与方法形参的绑定方法:
1.通过注解进行绑定 @RequestParam
使用注解进行绑定,我们只要在方法参数前面声明@RequestParam("userName"),就可以将request中参数a的值绑定到方法的该参数上。
2.通过参数名称进行绑定
使用参数名称进行绑定的前提是必须要获取方法中参数的名称,Java反射只提供了获取方法的参数的类型,并没有提供获取参数名称的方法。
SpringMVC解决这个问题的方法是用asm框架读取字节码文件,来获取方法的参数名称。
asm框架是一个字节码操作框架,关于asm更多介绍可以参考它的官网。个人建议,使用注解来完成参数绑定,这样就可以省去asm框架的读取字节码的操作。
二、SpringMVC源码分析
2.1 实现Controller接口源码流程
2.1.1 流程图
我们根据工作机制中三部分来分析SpringMVC的源码
1. ApplicationContext初始化时建立所有Url和Controller类的对应关系(用Map保存)。
2. 根据请求Url找到对应的Controller,并从Controller中找到处理请求的方法。
3. Request参数绑定到方法的形参,执行方法处理请求,并返回结果视图。
2.1 建立Map<url, controller>的关系
我们首先看第一个步骤,也就是建立Map<url,controller>关系的部分。第一部分的入口类为ApplicationObjectSupport的setApplicationContext方法。
setApplicationContext方法中核心部分就是初始化容器initApplicationContext(context),子类AbstractDetectingUrlHandlerMapping实现了该方法,所以我们直接看子类中的初始化容器方法。
@Override
public void initApplicationContext() throws ApplicationContextException {
super.initApplicationContext();
// 检测出handler
detectHandlers();
}
// 建立当前ApplicationContext中的所有Controller和Url的对应关系
protected void detectHandlers() throws BeansException {
ApplicationContext applicationContext = obtainApplicationContext();
if (logger.isDebugEnabled()) {
logger.debug("Looking for URL mappings in application context: " + applicationContext);
}
// 获取ApplicationContext容器中所有Bean的Name
String[] beanNames = (this.detectHandlersInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
applicationContext.getBeanNamesForType(Object.class));
// Take any bean name that we can determine URLs for.
// 遍历beanNames,并找到这些Bean对应的Url
for (String beanName : beanNames) {
// 找bean上的所有Url(Controller上的Url + 方法上的Url),该方法由对应的子类实现
String[] urls = determineUrlsForHandler(beanName);
if (!ObjectUtils.isEmpty(urls)) {
// URL paths found: Let's consider it a handler.
// 注册handler(重点)
// 保存url和beanName的对应关系
// put it to Map<urls,beanName>,该方法在父类AbstractUrlHandlerMapping中实现
registerHandler(urls, beanName);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");
}
}
}
}
// 获取controller中所有方法的url,由子类实现,典型的模板模式
protected abstract String[] determineUrlsForHandler(String beanName);
determineUrlsForHandler(String beanName)方法的作用是获取每个controller中的url,不同的子类有不同的实现,这是一个典型的模板设计模式。
BeanNameUrlHandlerMapping是AbstractDetectingUrlHandlerMapping的子类,处理beanName形式的url映射。
我们这里以BeanNameUrlHandlerMapping来进行分析。我们看BeanNameUrlHandlerMapping是如何查beanName上所有映射的url。
BeanNameUrlHandlerMapping#determineUrlsForHandler
// 获取Controller中所有的url
@Override
protected String[] determineUrlsForHandler(String beanName) {
List<String> urls = new ArrayList<>();
// 判断beanName,哪些是以 "/" 开头
if (beanName.startsWith("/")) {
urls.add(beanName);
}
String[] aliases = obtainApplicationContext().getAliases(beanName);
for (String alias : aliases) {
if (alias.startsWith("/")) {
urls.add(alias);
}
}
return StringUtils.toStringArray(urls);
}
AbstractUrlHandlerMapping#registerHandler
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Assert.notNull(urlPath, "URL path must not be null");
Assert.notNull(handler, "Handler object must not be null");
Object resolvedHandler = handler;
// Eagerly resolve handler if referencing singleton via name.
if (!this.lazyInitHandlers && handler instanceof String) {
// 转为beanName
String handlerName = (String) handler;
ApplicationContext applicationContext = obtainApplicationContext();
if (applicationContext.isSingleton(handlerName)) {
// 根据beanName获取bean,对应到我们的controller类
resolvedHandler = applicationContext.getBean(handlerName);
}
}
Object mappedHandler = this.handlerMap.get(urlPath);
if (mappedHandler != null) {
if (mappedHandler != resolvedHandler) {
throw new IllegalStateException(
"Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
"]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
}
}
else {
if (urlPath.equals("/")) {
if (logger.isInfoEnabled()) {
logger.info("Root mapping to " + getHandlerDescription(handler));
}
setRootHandler(resolvedHandler);
}
else if (urlPath.equals("/*")) {
if (logger.isInfoEnabled()) {
logger.info("Default mapping to " + getHandlerDescription(handler));
}
setDefaultHandler(resolvedHandler);
}
else {
// 最终put到map集合中,Map<url,controller>
this.handlerMap.put(urlPath, resolvedHandler);
if (logger.isInfoEnabled()) {
logger.info("Mapped URL path [" + urlPath + "] onto " + getHandlerDescription(handler));
}
}
}
}
到这里HandlerMapping组件就已经建立所有url和controller的对应关系
2.2 根据访问url找到对应controller中处理请求的方法
下面我们开始分析第二个步骤,第二个步骤是由请求触发的,所以入口为DispatcherServlet。
DispatcherServlet的核心方法为doService(),doService()中的核心逻辑由doDispatch()实现,我们查看doDispatch()的源代码。
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变量
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 1.检查是否是文件上传的请求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
// 2.取得处理当前请求的controller,这里也称为handler处理器,第一个步骤的意义就在这里体现了
// 这里并不是直接返回controller,而是返回的HandlerExecutionChain请求处理器链对象,该对象封装了handler和interceptors
mappedHandler = getHandler(processedRequest);
// 如果handler为空,则返回404
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 推断适配器,不同的controller类型,交给不同的适配器去处理
// 如果是一个bean,mappedHandler.getHandler()返回的是一个对象
// 如果是一个method,mappedHandler.getHandler()返回的是一个方法
// 3.获取处理request的处理器适配器handlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 到这里,spring才确定我要怎么反射调用
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
// 处理last-modified请求头
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 4.拦截器的预处理方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 通过适配器,处理请求(可以理解为,反射调用方法)(重点)
// 5.实际的处理器处理请求,返回结果视图对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 结果视图对象的处理
applyDefaultViewName(processedRequest, mv);
// 6.拦截器的后处理方法
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步:getHandler(processedRequest)方法实际上就是从HandlerMapping中找到url和controller的对应关系。这也就是第一个步骤:建立Map<url,Controller>的意义。
我们知道,最终处理request的是controller中的方法,我们现在只是知道了controller,还要进一步确认controller中处理request的方法。
由于下面的步骤和第三个步骤关系更加紧密,直接转到第三个步骤。
2.3 反射调用处理请求的方法,返回结果视图
上面的方法中,第2步其实就是从第一个步骤中的Map<urls,beanName>中取得Controller,然后经过拦截器的预处理方法,到最核心的部分--第5步调用controller的方法处理请求。
在第2步中我们可以知道处理request的Controller,第5步就是要根据url确定Controller中处理请求的方法,然后通过反射获取该方法上的注解和参数,解析方法和参数上的注解,最后反射调用方法获取ModelAndView结果视图。
因为上面采用注解url形式说明的,所以我们这里继续以注解处理器适配器来说明。第5步调用的就是AnnotationMethodHandlerAdapter的handle().handle()中的核心逻辑由invokeHandlerMethod(request, response, handler)实现。
三、源码Debug分析
3.1 SpringMVC初始化流程
3.2 DispatcherServlet入口
3.3 DispatcherServlet流程
我们分析@Controller、@RequestMapping注解形式的