Spring MVC —— 关于 WebApplicationInitializer
前言
从 WebApplicationInitializer
入手了解代码驱动 Spring web MVC
的模式及部分细节
版本
Spring 5.3.x
SpringServletContainerInitializer
// 它会收集所有 WebApplicationInitializer 的实现类
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = Collections.emptyList();
// 实例化所有的 WebApplicationInitializer 实现类
if (webAppInitializerClasses != null) {
initializers = new ArrayList<>(webAppInitializerClasses.size());
for (Class<?> waiClass : webAppInitializerClasses) {
// 必须是实现类
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
}
}
}
}
// 执行它们的 onStartup 方法
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
- 它是
Spring
提供的ServletContainerInitializer
的子类,关于ServletContainerInitializer
,它是Servlet
提供的一个SPI
,Sevlet
容器在启动时会执行对应实现类比如SpringServletContainerInitializer
,以上都是Servlet
提供的能力 - 该类会收集所有
WebApplicationInitializer
的实现类(由注解@HandlesTypes(WebApplicationInitializer.class)
决定)并执行它们的onStartup
,这就是Spring
进行的拓展了,依此实现基于Class
的容器配置
WebApplicationInitializer
public interface WebApplicationInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
- 顶层接口
WebApplicationInitializer
- 提供了方法
onStartup
,基于此来拓展ServletContext
AbstractContextLoaderInitializer
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
// ...
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
protected void registerContextLoaderListener(ServletContext servletContext) {
// 创建根容器
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
// 注册一个 ContextLoaderListener
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
// 允许指定 ApplicationContextInitializer
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
else {
}
}
// 创建根容器,子类实现
@Nullable
protected abstract WebApplicationContext createRootApplicationContext();
// 子类可以指定 ApplicationContextInitializer
@Nullable
protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
return null;
}
}
WebApplicationInitializer
的抽象子类- 它主要是创建
根容器
(WebApplicationContext
) 并给ServletContext
注册了一个ContextLoaderListener
ContextLoaderListener
会在contextInitialized
阶段配置并启动(refresh
)根容器,比如配置容器的configLocations
等- 根容器会被绑定在
ServletContext
上 - 至此,
Servlet
初始化后会被创建一个WebApplicationContext
启动并作为根容器
绑定
AbstractDispatcherServletInitializer
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
// 创建 web 子容器
WebApplicationContext servletAppContext = createServletApplicationContext();
// 创建并注册 DispatcherServlet 实例,子类可以拓展
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
dispatcherServlet.setContetInitializers(getServletApplicationContextInitializers());
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
// ...
}
// ...
}
AbstractContextLoaderInitializer
的抽象子类- 它主要是创建
web 子容器
,并为ServletContext
创建一个DispatcherServlet
DispatcherServlet
,Spring MVC
的核心Servlet
,主要负责路由的匹配、分发、执行、视图返回等- 不同于
ContextLoaderListener
驱动的根容器
,web 子容器
由DispatcherServlet
驱动,因为web 子容器
不像根容器
,它有自己独有的组件,以实现web MVC
的能力 - 具体的细节可见后续章节学习
DispatcherServlet
根容器 和 web子容器 更多是一个逻辑上的分层
根容器:通常包含的是通用的 bean 组件,即普通的 Component
web 子容器:通常包含的是 web 组件,比如 @Controller 类来
负责请求的路由与处理等
AbstractAnnotationConfigDispatcherServletInitializer
public abstract class AbstractAnnotationConfigDispatcherServletInitializer
extends AbstractDispatcherServletInitializer {
/**
* 根容器:创建 AnnotationConfigWebApplicationContext 实例并注册
* getRootConfigClasses 方法返回的配置类
*/
@Override
@Nullable
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(configClasses);
return context;
}
// 可能没有
else {
return null;
}
}
/**
* web子容器:创建 AnnotationConfigWebApplicationContext 实例并注册
* getServletConfigClasses 方法返回的配置类
*/
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
context.register(configClasses);
}
return context;
}
// 子类指定根容器配置类
@Nullable
protected abstract Class<?>[] getRootConfigClasses();
// 子类指定子容器配置类
@Nullable
protected abstract Class<?>[] getServletConfigClasses();
}
- 最完整的抽象基类,一般都是基于此类配置
- 它实现了父类留下的
createRootApplicationContext
方法和createServletApplicationContext
,来创建对应的根容器
和web 子容器
实例 - 我们提供的最终实现只需要指定容器读取的对应配置类即可
- 其中
根容器
可以不指定配置类,则不创建,也就是可能不存在根容器
,但子容器一定要存在,因为web 组件
都在子容器中
demo
至此,如果基于编程(而不是传统的 web.xml
)来配置 Spring Web MVC
容器就比较清晰了,见示例:
public class MyWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { RootConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
-------------- RootConfig
@Configuration
@ComponentScan({
"com.xsn.demo.spring.webmvc.service"
, "com.xsn.demo.spring.webmvc.dao"
})
public class RootConfig {
// 可以注册根容器组件 ...
}
-----------------WebConfig
@Configuration
@ComponentScan({
"com.xsn.demo.spring.webmvc.controller"
})
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
// 可以注册web子容器组件 ...
// 可以基于 WebMvcConfigurer 拓展
}
- 如示例,我们提供自己的
MyWebInitializer
,只需要指定对应的配置类
即可 - 配置类可以作为驱动类配合诸如
@ComponengScan
注解来指定扫描路径,同时也可以注册单独的组件 - 容器的父子关系就是基于
ApplicationContext
管理的,因此子容器
可见根容器
组件,反之则不可见 - 至此,不需要额外的配置,使用
Web 容器(Tomcat)
启动应用即可正常访问
总结
至此,我学习了如何基于 编程 配置 Spring Web MVC
应用,同时对其原理也有了大体的认识:Spring
替我们创建了一个 DispatcherServlet
对象,它持有基于我们指定配置类创建的 AnnotationConfigWebApplicationContext
实例,但是还有以下问题还待了解:
ContextLoadListener
监听并启动(refresh
)我们的根容器,那web 子容器
又在何时被谁(DispatcherServlet
)启动(refresh
)DispatcherServlet
基于web 子容器
创建哪些Spring web 组件
DispatcherServlet
处理请求的更多流程细节- 关于上述可在下文了解