SpringMVC源码分析
1、初始化
我们都知道,在一般使用Spring mvc框架时候都会在web app文件夹下的WEB-INF里面servlet的配置,那么其实在tomcat启动的时候,就会读取这个文件,并调用DispatcherServlet中的init方法开始初始化。但由于DispatcherServlet这个类没有重写这个init方法,所以会掉父类的init方法
源码入口org.springframework.web.servlet.HttpServletBean#init
代码走读:
- 初始化入口
org.springframework.web.servlet.HttpServletBean#init - 进一步初始化
org.springframework.web.servlet.FrameworkServlet#initServletBean - 初始化容器 (重要!)
org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext - 查找父容器(有就返回父容器,没有返回null)
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
因为webApplicationContext是servlet的一个属性,其实是可以配置的,所以如果配置的话,那么就不用初始化,就将父容器作为一个属性set过去就行了。 - 我们默认没有配置,那么就会走创建容器的代码
org.springframework.web.servlet.FrameworkServlet#createWebApplicationContext(org.springframework.web.context.WebApplicationContext)
org.springframework.web.servlet.FrameworkServlet#createWebApplicationContext(org.springframework.context.ApplicationContext)
看到这其实就差不多了,不过多展开创建过程。简单理解就通过一个工具类创建了一个容器
org.springframework.beans.BeanUtils#instantiateClass(java.lang.Class)
默认会生成一个XmlWebApplicationContext容器,是Servlet中的contextClass的默认值,也可以通过配置文件修改。然后会继续拿ContextConfigLocation的值,这里也是我们的配置文件配置的springBean的配置文件 - 执行初始化容器代码
org.springframework.web.servlet.FrameworkServlet#configureAndRefreshWebApplicationContext(){
//在这里会有一行重要的代码,作用简单理解就是给容器添加一个监听器。
//这个监听器的作用就是监听容器reFresh()方法执行完,开始做一些逻辑
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
//再往下就是refresh容器了,跟spring容器的初始化一样,这里不做展开,有兴趣可以看一下我的上一篇文章,有关spring的源码。
wac.refresh();
}
接下来重点研究一下这个ContextRefreshListener监听器,当监听到容器refresh会执行下面的方法
org.springframework.web.servlet.FrameworkServlet.ContextRefreshListener#onApplicationEvent
- 进到Servlet的onApplicationEvent方法中
org.springframework.web.servlet.FrameworkServlet#onApplicationEvent - 再然后就是DispatcherServlet的onRefresh方法,
其实可以理解为Servlet的初始化方法
org.springframework.web.servlet.DispatcherServlet#onRefresh
org.springframework.web.servlet.DispatcherServlet#initStrategies
我们着重看一下里面的逻辑:
protected void initStrategies(ApplicationContext context) {
this.initMultipartResolver(context);
this.initLocaleResolver(context);
this.initThemeResolver(context);
//重要:初始化请求映射
this.initHandlerMappings(context);
//重要:加载一些适配器,比如参数转换的适配器,返回值转换的适配器等
this.initHandlerAdapters(context);
this.initHandlerExceptionResolvers(context);
this.initRequestToViewNameTranslator(context);
this.initViewResolvers(context);
this.initFlashMapManager(context);
}
关于initHandlerMappings
我们先来看一下org.springframework.web.servlet.DispatcherServlet#initHandlerMappings这个方法里面的逻辑
大致逻辑是首先会在beanFactory中找HandlerMapping的类,如果有的话赋值给handlerMappings,如果没有就走默认创建逻辑。
org.springframework.web.servlet.DispatcherServlet#getDefaultStrategies
此方法会去加载DispatcherServlet.properties的配置文件中的类,其中默认配置的类有
org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,
org.springframework.web.servlet.function.support.RouterFunctionMapping
在这三个类在创建过程中,每个又都会有各自的初始化方法
BeanNameUrlHandlerMapping
它的父类实现了ApplicationContextAware接口,在重写setApplicationContext时候会调用org.springframework.context.support.ApplicationObjectSupport#initApplicationContext(org.springframework.context.ApplicationContext)方法
RequestMappingHandlerMapping
它的父类实现了InitializingBean接口,在重写的afterPropertiesSet方法中会去调用
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#initHandlerMethods 方法
RouterFunctionMapping
它的父类实现了InitializingBean接口,在重写的afterPropertiesSet方法中会去调用initRouterFunction、initMessageConverters等方法。
这三个类的初始化逻辑其实就是为了提前将请求路径和执行方法(类)做映射,比如RequestMappingHandlerMapping的初始化方法initHandlerMethods的代码走读如下:
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#initHandlerMethods
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#processCandidateBean
在这个方法中会去调用一个很重要的判断代码:
protected boolean isHandler(Class<?> beanType) {
return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class);
}
这个代码想必大家一看就知道了吧,就是要找出来那些handler即能处理请求的类即常说的controller
找到之后会去执行
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#detectHandlerMethods
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#getMappingForMethod
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#createRequestMappingInfo(java.lang.reflect.AnnotatedElement)
这段逻辑也有一段很重要的代码
@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
RequestMapping requestMapping = (RequestMapping)AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
RequestCondition<?> condition = element instanceof Class ? this.getCustomTypeCondition((Class)element) : this.getCustomMethodCondition((Method)element);
return requestMapping != null ? this.createRequestMappingInfo(requestMapping, condition) : null;
}
这段逻辑大致意思就是会去查找该类下的所有方法,判断每一个方法中是不是带有@RequestMapping,如果有会把@RequestMapping注解的的一些值封装成一个RequestMappingInfo对象。然后返回时候是一key为Method,value为RequestMappingInfo对象的map。
然后会遍历这个map,将handler(即controller类)、RequestMappingInfo和Method存到一个registry中去,方便后面使用。
BeanNameUrlHandlerMapping这个类的初始化方法和另外一种不常见的实现请求controller层有关(实现Controller接口或HttpRequestHandler)有兴趣的可以自己研究一下。
2、DispatcherServlet根据请求找对应处理方法
当一个请求过来时候,tomcat处理请求会去调用DispatcherServlet的service方法,所以我们看源码时候,这个方法就是处理请求的入口:
org.springframework.web.servlet.FrameworkServlet#service
当你不是patch请求方式请求时候,会调用父类的
javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
这个方法的逻辑大概是会根据请求方式去调对应的方式,比如get请求会去调doGet方法,post请求会去调doPost方法等等。不过再往下走进入源码发现,无论是什么请求都最终会进入一个方法叫做
org.springframework.web.servlet.FrameworkServlet#processRequest
里面会去调用org.springframework.web.servlet.DispatcherServlet#doService
然后去调用
org.springframework.web.servlet.DispatcherServlet#doDispatch
这是一个真正的处理的逻辑
1、首先走进一个getHandler的方法
org.springframework.web.servlet.DispatcherServlet#getHandler
此方法的大致逻辑是遍历上面说的那三个默认的HandlerMapping,调用他们的getHandler,会去之前存储的映射关系中找到对应的处理类(实现Controller接口的形式的)或者处理类方法(注解@RequestMapping形式的)然后还有对应的适配器、拦截器链等封装成一个HandlerExecutionChain返回。
2、然后继续回到doDispatch主逻辑来,接下来就是从返回的HandlerExecutionChain中拿到对应的适配器
org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter
3、然后执行所有拦截器的preHandle
org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle
如果不通过就会直接返回了
4、然后才会真正的执行,返回一个moduleAndView对象
org.springframework.web.servlet.HandlerAdapter#handle
5、然后设置一个默认的视图名 applyDefaultViewName
6、然后执行所有拦截器的postHandle
org.springframework.web.servlet.HandlerExecutionChain#applyPostHandle
7、最后才是处理返回结果,渲染视图org.springframework.web.servlet.DispatcherServlet#processDispatchResult
值得注意的是,在最终渲染完成之后,才会最终调用所有拦截器的afterCompletion方法