SpringMVC文档、源码瞎读——两种整合方式
概述
本篇文章介绍了SpringMVC官方文档中的两种DispatcherServlet的整合方式,并且通过源码分析它们是如何整合起来的。
同时,看这篇文章,有一些前置知识需要掌握:
默认认为看这篇文章的朋友已经大体掌握了Spring的BeanFactory和ApplicationContext的源码。
因为本人不太会画图,所以文章中较少出现图示,都是文字和代码块,我尽量用简洁的语言来描述源码执行流程。
由于源码分析的过程中常常会深入到某一功能模块中,以至于你可能从该功能模块中出来时已经忘了该功能是为什么被使用的(被谁调用),并且源码可能有比较复杂的结构,所以必要时请使用右下角的目录功能,呼出目录,看看当前所处位置前后的章节结构层级关系。
实现WebApplicationInitializer
整合
使用SpringMVC时需要注册一个前端Servlet嘛,大家都知道,下面是官方的一个用例,该用例中使用了Servlet3.0 API中提供的ServletContainerInitializer
,以SPI方式发现我们的WebApplicationInitializer
。这部分的原理可以看我的这篇文章
public class SpringWebMVCAdapterWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 创建WebApplicationContext
// 注册配置类
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(AppConfig.class);
// 注册DispatcherServlet
DispatcherServlet dispatcherServlet = new DispatcherServlet(context);
ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcherServlet", dispatcherServlet);
registration.setLoadOnStartup(1);
registration.addMapping("/*");
}
}
上面的代码中,通过实现WebApplicationInitializer
来监测Web应用容器——ServletContext的启动,并在它启动时做这些事:
- 创建一个
AnnotationConfigWebApplicationContext
(这大概是一个可以使用注解配置的,具有Web相关功能的ApplicationContext) - 向context中注册一个组件类(AppConfig.class)进去,使用该组件做Spring相关的配置。
- 创建
DispatcherServlet
,传入context,向ServletContext中添加该Servlet并配置该Servlet
虽然我没有写过纯注解的ApplicationContext源码解析的文章,但我也稍稍阅读过相关源码,所有被注册到这种ApplicationContext的组件在refresh操作后都会被注册成Bean。并且,如果它指定了组件扫描,或者它其中使用@Bean
注解定义了其它Bean,这些东西都会被正确的扫描并添加到Context中。所以,带来的结果就是,refresh后,AppConfig
被注册成了一个Bean,而top.yudoge
包下所有的组件类都被扫描进了Context,但我们现在还不知道是谁做了refresh操作。
在top.yudoge.controller
包下创建一个简单的Controller:
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "hello, this is hello controller!";
}
}
由于前面的AppConfig
组件被注册到了ApplicationContext中,所以当refresh操作后,在它指定自动扫描包下的HelloController
也被注册到了ApplicationContext中,现在运行程序,该Controller应该能正常工作了。
下面我们将开始DispatcherSerlvet初始化源码分析:
构造器
我们注意到,在上面的示例中创建DispatcherServlet时,ApplicationContext是会被传进去的。
DispatcherServlet dispatcherServlet = new DispatcherServlet(context);
DispatcherServlet的这个构造器里调用了父构造器,并把context传入。
public DispatcherServlet(WebApplicationContext webApplicationContext) {
super(webApplicationContext);
setDispatchOptionsRequest(true);
}
父类的方法也只是把它设置给了一个成员变量,然后就啥也没干
public FrameworkServlet(WebApplicationContext webApplicationContext) {
this.webApplicationContext = webApplicationContext;
}
我想总会有一个初始化方法对传入的ApplicationContext做点什么,看看DispatcherServlet
的继承结构,HttpServlet
的init
方法会在Servlet被初始化时触发(对于本例也就是Servlet容器启动时),估计在这里做了什么手脚。
@Override
public final void init() throws ServletException {
// 从Servlet的初始化参数中设置Bean属性
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {}
}
// 允许子类做任何形式的初始化
initServletBean();
}
FrameworkServlet中有一些属性,下图只展示一部分,上面的操作会把声明DispatcherServlet时指定的init-parameter设置到这些属性中:
所以,我们之前使用xml方式配置DispatcherServlet时总要在init-parameter中使用contextConfigLocation
参数指定配置文件位置呢。
然后,使用模板模式调用了子类的initServletBean
方法,也就是FrameworkServlet.initServletBean
FrameworkServlet.initServletBean
@Override
protected final void initServletBean() throws ServletException {
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
} catch (ServletException | RuntimeException ex) {throw ex;}
}
果然,这里调用了initWebApplicationContext
对传入的ApplicationContext进行了一波操作,然后调用initFrameworkServlet
又做了其它的初始化操作。
FrameworkServlet.initWebApplicationContext
这个方法首先尝试使用该构造方法中传入的ApplicationContext,如果没有传入,就尝试在该Servlet配置中寻找一个,如果依然没有,就创建一个本地的。所以我们有多种方法为SpringMVC提供一个WebApplicationContext。
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// 走到这个分支代表一个context实例在构造时已经被注入进来,使用它
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// 如果context还没被刷新
// 那么我们设置它的父context,配置并刷新它
if (cwac.getParent() == null) {
// 如果context实例没有它自己明确的父context,我们就设置rootContext为它的父context(可能为null)
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// 走到这个分支代表构造时并没有注入一个context实例
// 那么查看servlet上下文中是否有人注册了一个,如果有,假设它的父context已经被设置并且用户已经做了所有初始化操作
wac = findWebApplicationContext();
}
if (wac == null) {
// 如果这个servlet中并没有设置任何context实例,创建一个本地的
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
构造器传入context的情况
在构造器传入context时,Servlet会检测它是否是一个可配置的context,如果是就尝试配置并refresh它。对于我们传入的context,它明显满足这些条件,我们在configureAndRefreshWebApplicationContext
上打个断点,直接看看它执行完,context发生了什么变化。
在那个方法结束后,AppConfig
和HelloServlet
的BeanDefinition都已经被创建,并且ApplicationContext的老五件儿BeanPostProcesser也被作为Bean注册了进来。
尝试使用xml配置文件
把我们的Initializer
的代码改成这样,通过contextConfigLocation
这个init-parameter
来设置spring配置文件
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcherServlet", dispatcherServlet);
registration.setInitParameter("contextConfigLocation", "classpath*:spring-config.xml");
registration.setLoadOnStartup(1);
registration.addMapping("/*");
}
提供spring-config.xml
,在其中配置HelloController
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="top.yudoge.controller"/>
</beans>
运行,成功
Debug,发现这个分支被执行了:
这证明在XML方式下,FrameworkServlet会自己创建一个WebApplicationContext,并且这个WebApplicationContext是一个XmlWebApplicationContext
的实例,xml
中没有开启context
命名空间,所以beanDefinitionMap中没有什么乱七八糟的东西,只有一个helloController
这个分支下的createWebApplicationContext
方法很简单,我不贴出来了,就是读取contextConfigLocation
并创建一个XmlWebApplicationContext(可以修改成其他类)
findWebApplicationContext在什么情况下使用
在initWebApplicationContext
中的三个创建WebApplicationContext的方法已经了解俩了:
- 已经了解:如果外界传入context,使用外界传入的
- 尚未了解:否则,使用
findWebApplicationContext
,若返回值不为null,使用返回值 - 已经了解:否则,如果定义了
contextConfigLocation
,基于这个位置解析XML文件,构造一个XmlWebApplicationContext并使用
那么findWebApplicationContext
究竟在啥时候用到的?看看代码:
@Nullable
protected WebApplicationContext findWebApplicationContext() {
// 这里其实就是读取一个叫`this.contextAttribute`的属性
// 这个属性默认为null,所以该方法作用不上
String attrName = getContextAttribute();
if (attrName == null) {
return null;
}
// 如果找到属性,这个方法实际上是从ServletContext的上下文里通过getAttribute获取一个WebApplicationContext
WebApplicationContext wac =
WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
return wac;
}
所以,根据代码,我们明显可以看到,它是根据字符串类型的成员变量contextAttribute
来尝试去ServletContext
中获取属性,如果这个属性是一个WebApplicationContext
,那么就使用这个。下面修改我们的代码:
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(AppConfig.class);
// 手动刷新,因为那个分支假设我们刷新过了
context.refresh();
DispatcherServlet dispatcherServlet = new DispatcherServlet();
ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcherServlet", dispatcherServlet);
// 向ServletContext设置属性,将WebApplicationContext设置进去
servletContext.setAttribute("webApplicationContext", context);
// 通过initParameter来设置`contextAttribute`变量
registration.setInitParameter("contextAttribute", "webApplicationContext");
registration.setLoadOnStartup(1);
registration.addMapping("/*");
}
运行结果:
总结
上面我们已经了解了实现WebApplicationInitailizer
来将SpringMVC整合到JavaWeb项目中的全过程。
将SpringMVC整合到JavaWeb中,主要靠DispatcherServlet
,不管你是通过我们尚未演示的XML方式来声明它还是通过实现WebApplicationInitializer
来以编程式的方式创建它,你都得和它打打交道。
除此之外,你还需要给DispatcherServlet
指定一个WebApplicationContext
来管理项目中的所有Bean和组件,通过如下三种方式指定:
- 自己构造,并传入
DispatcherServlet
- 向
DispatcherServlet
添加init-parameter
,设置contextConfigLocation
参数为SpringXML配置文件的位置。DispatcherServlet
会自动创建一个XMLWebApplicationContext
- 通过向
ServletContext
中添加一个属性值为WebApplicationContext
的属性,并向DispatcherServlet
添加init-parameter
,设置contextAttribute
属性为context的属性名
继承AbstractAnnotationConfigDispatcherServletInitializer整合
这个AbstractAnnotationConfigDispatcherServletInitialzer
名字好长。。。
从名字分析,它是一个提供了一些模板功能的让我们继承的Initialzier
,在里面很多工作不用我们手动完成了,而且它提供的功能大概是帮我们自动配置DispatcherServlet,并使用注解配置的ApplicationContext。
大家应该都使用过这个类,我们先配置它,把HelloController的用例搭建起来:
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
// 指定配置类
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] {AppConfig.class};
}
// 指定DispatcherServlet的匹配路径
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
运行:
完全没有手动进行DispatcherServlet、ApplicationContext的配置,但SpringMVC已然整合完毕。
概念:context层次结构
为什么上面要提供两个返回配置类列表的方法?RootConfig和ServletConfig有什么区别吗?
继续之前,需要先搞清一个概念。SpringMVC提供了这样的context层次结构:
- Servlet WebApplicationContext(下文统称ServletWebContext):保存Controller、ViewResolver等Web相关的基础设施Bean
- Root WebApplicationContext(下文统称RootContext):保存中间层Service、数据源等非Web的基础设施Bean
- 在context层次结构中,ServletWebContext是RootContext的子context,DispatcherServlet直接和ServletWebContext交流,但对于无法满足的要求,ServletWebContext会委托RootContext来处理
为什么要这样呢?因为官方认为对于一个复杂的Web应用,有可能提供多个DispatcherServlet
,而它们之间可能需要复用相同的非Web层基础设施(Service、Repository、数据源等),所以提供这么两层。
而RootConfig就是被注入到RootContext中的配置类列表,ServletConfig就是被注入到ServletWebContext的配置类列表。
后文中可能出现多次ServletWebContext和ServletContext,请读者注意,ServletWebContext是Spring中的ApplicationContext,而ServletContext是ServletAPI中的代表Web应用的上下文对象。
下面我们将开始这种方式整合SpringMVC的源码分析。AbstractAnnotationConfigDispatcherServletInitializer
的继承关系图:
上面有三个为我们提供便利的AbstractXXXInitializer
,它们都或多或少的为我们实现了一些模板功能,下面,我们将一个一个的审视它们的职能。
AbstractContextLoaderInitializer
这个类是最复杂的一个类,所以可能要花最多时间,把它搞懂了,后面的都一马平川了
作为最底层的抽象Initializer,它直接实现了WebApplicationInitializer
,它做了如下工作:
- 向ServletContext中注册一个
ContextLoaderListener
- 委托子类创建一个RootContext,并传递给
ContextLoaderListener
ContextLoaderListener
实现了ServletContextListener
,也就是说它会在ServletContext启动和销毁时被回调,它的作用就是负责启动和关闭RootContext。
onStartup
在它的onStartup方法中,调用了registerContextLoaderListener
方法:
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
registerContextLoaderListener
方法通过调用createRootApplicationContext
来委托子类创建一个RootContext,并且只有当返回值不为null时它才会进行进一步的操作,也就是说,如果无必要,子类可以完全不创建这个RootContext,这时,这个类的工作就结束了,啥也没干。
protected void registerContextLoaderListener(ServletContext servletContext) {
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}
在子类创建并返回了一个RootContext的情况下(rootAppContext != null
),它会创建一个ContextLoaderListener
并把RootContext传进去,意思是让ContextLoaderListener
维护这个RootContext的创建关闭。
然后它调用getRootApplicationContextInitializers
方法来获取一些ContextInitializer
,目前我们还不知道是干啥的,但默认情况下这个方法返回空,也就是说子类可以重写这个方法来返回一些ContextInitializer
。
稍后,它把这个ContextLoaderListener
添加进ServletContext。
ContextLoaderListener做了什么
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener(WebApplicationContext context) {super(context);}
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
它实现了ServletContextListener
,所以contextInitialized
Web应用开始初始化阶段时被Servlet容器调用,contextDestroyed
在Web应用将要被shutdown时被Servlet容器调用。
同时,它继承了ContextLoader
,在contextInitialized
和contextDestroyed
中都直接调用了ContextLoader
中的方法。
initWebApplicationContext
initWebApplicationContext
是父类ContextLoader
的方法,在Web应用初始化阶段开始时被调用,看看它都做了啥:
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
try {
// 如果RootContext为null,自己创建一个
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
// 如果context是ConfigurableWebApplicationContext
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
// 如果尚未配置,进行配置
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
// 尝试加载父ApplicationContext,默认是null
// 这代表子类可以继续向RootContext添加父Context
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 配置并刷新
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 将RootContext设置到ServletContext的Attribute中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
// ...
return this.context;
}
catch (RuntimeException | Error ex) {}
}
看起来代码挺多,其实和之前FrameworkServlet
中的代码都差不多,所以我们很容易理解:
- 如果尚未设置RootContext,创建一个
- 如果RootContext是一个可配置WebApplicationContext,并且尚未激活,对它进行初始配置
- 将RootContext设置到ServletContext的Attribute中,使用的key是
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
closeWebApplicationContext
closeWebApplicationContext
是父类ContextLoader
的方法,在Web应用销毁阶段开始时被调用,看看它都做了啥:
// 省略后的代码
public void closeWebApplicationContext(ServletContext servletContext) {
try {
// 如果是可配置Web应用Context,尝试关闭
if (this.context instanceof ConfigurableWebApplicationContext) {
((ConfigurableWebApplicationContext) this.context).close();
}
}
finally {
// 将RootContext从ServletContext属性中移除
servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
}
AbstractContextLoaderInitializer的一个毫无用处的示例
我们继承AbstractContextLoaderInitializer
,在父类委托我们创建RootContext的方法中创建一个RootContext,并向其中注册一个组件——RootConfig.class
:
public class TestContextLoaderInitializer extends AbstractContextLoaderInitializer {
@Override
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext walc = new AnnotationConfigWebApplicationContext();
walc.register(RootConfig.class);
return walc;
}
}
RootConfig的代码:
@Configuration
public class RootConfig {}
创建一个Servlet:
@WebServlet("/test")
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 从ServletContext的属性中获取RootContext
WebApplicationContext context = (WebApplicationContext) req.getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
// 从RootContext中获取RootConfig组件
RootConfig rootConfig = context.getBean(RootConfig.class);
// 向前端输出
resp.getWriter().println(rootConfig);
}
}
我承认这个例子毫无用处,但是它有助于我们理解AbstractContextLoaderInitializer
都干了什么,运行程序并访问/test
,页面显示的内容代表我们成功拿到了RootConfig组件的实例:
回头审视FrameworkServlet中的代码
这下看看下面FrameworkServlet中高亮的几处代码,我们曾经忽略过它,如果已经忘了FrameworkServlet的可以回去看下。
第一处获取RootContext,第二处给我们传入的context设置父Context,我们传入的这个就是ServletWebContext喽。
protected WebApplicationContext initWebApplicationContext() {
// 获取rootContext
+ WebApplicationContext rootContext =
+ WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
+ cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
// ...
}
关键是RootContext是怎么获取的,我们可以看到它调用了一个Utils类的静态方法,我们进到这个静态方法中,发现它正是获取了ServletContext中的那个属性:
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
之前,我们没有继承AbstractContextLoaderInitializer
时,显然ServletContext的属性中没有这个东西,但现在有了,SpringMVC中的context层次结构被建立起来了。
emm,ServletContextListener的初始化方法在所有Servlet被创建之前调用,所以Framework能获取到ContextLoaderListener设置的这个属性
AbstractContextLoaderInitializer的一个有点用处的示例
现在,我们利用这个Initiailzer来整合DispatcherServlet,并且建立起官方推荐的context层次关系。
public class MyContextLoaderInitializer extends AbstractContextLoaderInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
private void registerDispatcherServlet(ServletContext servletContext) {
AnnotationConfigWebApplicationContext servletWebContext = new AnnotationConfigWebApplicationContext();
servletWebContext.register(AppConfig.class);
DispatcherServlet dispatcherServlet = new DispatcherServlet(servletWebContext);
ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcherServlet", dispatcherServlet);
registration.setLoadOnStartup(1);
registration.addMapping("/");
}
@Override
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(RootConfig.class);
return rootContext;
}
}
- 首先,调用父类的
onStartup
,父类会回调我们的createRootApplicationContext
来创建RootContext。 - 再调用
registerDispatcherServlet
来创建ServletWebContext,并创建DispatcherServlet,将它传到DispatcherServlet中
这样,FrameworkServlet会构建ServletWebContext和RootContext的父子关系
总结
AbstractContextLoaderInitializer
只负责委托子类创建RootContext,向ServletContext的属性中添加RootContext以便FrameworkServlet建立Context层级关系时利用。除此之外它啥也没干,它没有创建DispatcherServlet,没有将SpringMVC整合到Web应用中。
AbstractDispatcherServletInitializer
下面是层次结构中的第二层,AbstractDispatcherServletInitializer
,从名字上看,它是用来注册DispatcherServlet的。
它的onStartup
方法...和我们刚刚示例中写的竟然一样。没错,我是抄它的:
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
不过它的registerDispatcherServlet
和我们有所不同,因为它是模板类,不是一个项目中的顶层类(用于工作的类),所以它要委托顶层类(我们实现的类)去做一些工作:
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
// 委托子类创建一个ServletWebContext
WebApplicationContext servletAppContext = createServletApplicationContext();
// 创建DispatcherServlet,并将子类创建好的ServletWebContext设置进去
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
// 注册DispatcherServlet
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
registration.setLoadOnStartup(1);
// 委托子类返回DispatcherServlet应该匹配的URL
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
// 如果子类希望同时注册一些Filter,那么可以重写`getServletFilters`
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
// 如果子类想对DispatcherServlet做任何其它配置,可以重写`customizeRegistration`
customizeRegistration(registration);
}
代码的注释已经写的很详细了,不过还是总结一下:
- 调用父类的
onStartup
,允许父类创建一个RootContext - 委托子类创建ServletWebContext
- 创建DispatcherServlet,并传入ServletWebContext
- 注册DispatcherServlet
同时,该类给子类暴露的方法有:
createServletApplicationContext
:委托子类创建一个ServletWebContextgetServletMappings
:返回DispatcherServlet返回的URLgetServletFilters
:启动的同时注册一批FiltercustomizeRegistration
:允许子类对DispatcherServlet做进一步的配置createRootApplicationContext
:别忘了它父类的抽象方法,委托子类创建一个RootContext
AbstractAnnotationConfigDispatcherServletInitializer
下面,来到层次结构的顶层,该类直接被我们的Initializer给继承。同时,它的名字好像说明了它创建基于注解配置的WebApplicationContext。
它没有实现onStartup
,这样的话会调用到父类的onStartup
。该类的核心任务是创建ServletWebContext和RootContext,也就是实现createServletApplicationContext
方法和createRootApplicationContext
方法
createServletApplicationContext
@Override
protected WebApplicationContext createServletApplicationContext() {
// 创建基于注解配置的WebApplicationContext作为ServletWebContext
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
// 委托子类返回作用于该context的配置类
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
// 将配置类作为组件注册到context
context.register(configClasses);
}
return context;
}
createRootApplicationContext
@Override
@Nullable
protected WebApplicationContext createRootApplicationContext() {
// 委托子类返回作用于RootContext的配置类
Class<?>[] configClasses = getRootConfigClasses();
// 如果配置类列表不为空
if (!ObjectUtils.isEmpty(configClasses)) {
// 创建RootContext并注册配置类组件
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(configClasses);
return context;
}
// 如果配置类列表为空 -> 返回null
else {
return null;
}
}
这个方法和上一个有一些不同,上一个方法无论配置类列表是否为空都会创建ServletWebContext,而这个只有当配置类列表不为空时才创建RootContext。
总结
该类主要的作用就是创建基于注解配置的ServletWebContext和RootContext,同时向子类暴露:
getRootConfigClasses
:返回RootContext的配置类列表getServletConfigClasses
:返回ServletWebContext的配置类列表getServletMappings
:父类中未实现的方法,返回DispatcherServlet匹配的URL
拨云见日,回顾顶层
这下看看我们通过继承AbstractAnnotationConfigDispatcherServletInitializer
实现的Initalizer,我们已经完全知道它如何将SpringMVC整合到JavaWeb项目中了,并且我们知道了context层次结构如何被创建,如何注册自定义Filter等等等等内容
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] {AppConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
总结
通过继承AbstractAnnotationConfigDispatcherServletInitializer
的方式来整合SpringMVC,它帮我们创建了DispatcherServlet
,并通过我们自己定义的配置类创建Root WebApplicationContext
和Servlet ApplicationContext
,并于FrameworkServlet
协同工作完成context层级关系的创建。