01-SpringMVC启动过程分析
我们使用Servlet3.0的方式来配置DispatcherServlet,同样还是采用5.1.4.RELEASE版本的Spring。根据Spring官方文档的介绍,我们可以利用下面这段代码来配置
首先我来解释一下,在Servlet3.0的时候提出了可以通过SPI的方式动态注册一个Servlet,Spring正是利用这一点,通过SpringServletContainerInitializer来启动WebApplicationInitializer的实现类,因此我们可以通过实现WebApplicationInitializer接口,达到注册DispatcherServlet给Servlet容器(本文使用Tomcat)。从图中可以看到,一开始会实例化一个Spring容器(AnnotationConfigWebApplicationContext),并利用这个ApplicationContext来实例化DispatcherServlet。
首先我们来回顾一下Java中类的实例化过程
- 初始化父类静态变量(静态变量,静态代码块,静态方法,需要注意的是main方法也是静态的)
- 初始化子类静态变量(范围同父类)
- 初始化父类普通成员变量及方法
- 调用父类构造方法
- 初始化子类普通成员变量及方法
- 调用子类构造方法
那么,在实例化DispatcherServlet的时候也是严格按照这个顺序来执行的。我们按照这个思路来看看在不同阶段都做了些什么。
DispatcherServlet的注册过程
通过查看源码我们可以看到,在DispatcherServlet中存在大量的静态属性和一个静态代码块,他的父类FrameworkServlet中存在少量静态属性。这些属性大多都是用来命名的常量,我们需要关注一下这个静态代码块。
通过上图可以看到,在这个静态代码块中初始化了DispatcherServlet的默认策略,这些策略在后面的onRefresh方法中会用到。
结束静态资源的初始化之后最后会调用构造方法,在构造方法中会将传进来的ApplicationContext赋值给FrameworkServlet的webApplicationContext属性。随后会配置Servlet的名称及URLMapping。
至此,DispatcherServlet就已经被注册到Servlet容器中去了,随后在容器启动时会调用GenericServlet的init方法。这个在我们学习Servlet时应该已经学习过了。Spring通过重写init方法来真正启动SpringMVC模块。让我们来关注init过程。
HttpServletBean关键代码
通过上述代码可以看到,Spring会先读取Servlet配置的参数,即Servlet的InitParam。如果没有配置则不做操作,紧接着初始化ServletBean,但这是个抽象方法,具体实现在FrameworkServlet中,我们进入子类中查看相关代码
FrameworkServlet关键代码
/**
* Initialize and publish the WebApplicationContext for this servlet.
* <p>Delegates to {@link #createWebApplicationContext} for actual creation
* of the context. Can be overridden in subclasses.
* @return the WebApplicationContext instance
* @see #FrameworkServlet(WebApplicationContext)
* @see #setContextClass
* @see #setContextConfigLocation
*/
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
// 注意,此前我们在注册DispatcherServlet时手动创建了一个ApplicationContext并将它赋值给了webApplicationContext,因此这里不为空,若采用旧的web.xml方式注册DispatcherServlet则这里为空
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
// 如果是传统方式会在这里创建ApplicatinContext
wac = createWebApplicationContext(rootContext);
}
//
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
// 这里会调用DispatcherServlet的onRefresh方法,执行初始化操作
onRefresh(wac);
}
}
return wac;
}
onRefresh方法关键部分
onRefresh方法会利用之前提到的默认策略(没有自定义策略的前提下)来初始化相关功能,关键代码如下:
/**
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
// 初始化文件上传解析器
initMultipartResolver(context);
// 初始化国际化解析器
initLocaleResolver(context);
// 初始化主题解析器
initThemeResolver(context);
// 初始化HandlerMapping,默认是RequestMappgingHandlerMapping和SimpleUrlHandlerMapping,其中RequestMappingHandlerMapping是用来处理@RequestMapping的,而SimpleUrlHandlerMapping是用来维护Uri路径模式的显示注册
initHandlerMappings(context);
// 注册HandlerAdapter,默认是RequestMappingHandlerAdapter,HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter这三种
initHandlerAdapters(context);
// 初始化异常处理器
initHandlerExceptionResolvers(context);
// 初始化视图名称转换器
initRequestToViewNameTranslator(context);
// 初始化视图解析器
initViewResolvers(context);
// 初始化重定向属性管理器
initFlashMapManager(context);
}
至此我们已经可以了解了Spring MVC在启动的大概流程,大致了解Spring MVC都做了什么。后续会继续分析请求到达Dispatcher Servlet后是如何找到对应的controller以及参数封装,最后再到数据返回等流程的详细过程。
由于本人能力有限,难免会有表述不清或错误的地方,还希望各位不吝指教,大家共同学习,一起进步。