Spring MVC基于Java Config配置的启动流程分析
本文将分析Spring MVC基于Java Config配置的启动流程,由于基于Java Config配置的核心启动流程与基于XML配置的启动流程类似,所以阅读本文前请先阅读Spring MVC启动流程分析(xml配置)、Spring MVC DispatcherServlet处理用户请求的流程分析、Spring MVC之Servlet2.x与Servlet3.x的区别这三篇文章。下面首先给出Java Config配置的Spring MVC Web应用的例子。
一、Spring MVC Java Config配置示例
1. 定义AbstractAnnotationConfigDispatcherServletInitializer实现
// 基于Java Config的Spring MVC初始化器(WebApplicationInitializer实现)
public class MySpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class}; // build ROOT WebApplicationContext
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class}; // build DispatcherServlet WebApplicationContext
}
@Override
protected String[] getServletMappings() { // DispatcherServlet Mapping
return new String[]{"/"};
}
}
2. WebConfig(DispatcherServlet WebApplicationContext)
// DispatcherServlet对应的上下文的Java Config配置
@Configuration
@ComponentScan("com.jay.anno.web")
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Bean
public ViewResolver viewResolver() { // 自定义ViewResolver
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views");
viewResolver.setSuffix(".jsp");
viewResolver.setExposeContextBeansAsAttributes(true);
return viewResolver;
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable(); // 静态资源使用DefaultServlet处理
}
}
// 示例Controller
@RestController
public class HelloController {
@GetMapping("/hello")
public String sayHello() {
return "hello";
}
}
3. RootConfig(ROOT WebApplicationContext)
// ROOT应用上下文Java Config配置
@Configuration
@ComponentScan(basePackages = "com.jay.anno.root",
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)})
public class RootConfig {
}
以上三个类构成了Spring MVC基于Java Config的核心配置。这种配置下,无须再配置web.xml文件。Web容器(如Tomcat,需支持Servlet3.0,依赖于Servlet3.0的特性)能自动发现MySpringMvcInitializer类(WebApplicationInitializer实现)并进行相关初始化流程。
二、启动流程分析
从Spring MVC之Servlet2.x与Servlet3.x的区别这篇文章可知,ServletContainerInitializer接口(Servlet 3.0)主要由框架(如Spring MVC)实现,在应用程序启动时由Web容器调用其onStartup()方法,负责框架的初始化动作,如初始化控制器Servlet(对应Spring MVC,DispatcherServlet)、连接数据库等。而Web容器对于ServletContainerInitializer实现的查找,是通过Java SPI机制实现的。首先看下Spring MVC对ServletContainerInitializer接口的实现——SpringServletContainerInitializer的配置:
因此,Web应用在依赖了Spring MVC相关依赖后,并部署到Tomcat等Web容器(Servlet 3.0)并启动时,会由容器调用SpringServletContainerInitializer.onStartup()方法。SpringServletContainerInitializer类如下:
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
// 省略...
}
}
基于Servlet 3.0的特性,SpringServletContainerInitializer实现了ServletContainerInitializer接口,同时注解了@HandlesTypes(WebApplicationInitializer.class),表示该SpringServletContainerInitializer只关心类路径下WebApplicationInitializer接口相关的实现类。因此Web容器在回调SpringServletContainerInitializer.onStartup方法时会将类路径下匹配的WebApplicationInitializer接口相关实现类传入onStartup方法中。此时再来看第一部分示例中的MySpringMvcInitializer类,这个类实现了WebApplicationInitializer接口,因此在应用启动时会将该类传入SpringServletContainerInitializer.onStartup方法。下面分析下SpringServletContainerInitializer的实现:
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer) // 实例化WebApplicationInitializer
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers); // 排序
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext); // 回调WebApplicationInitializer.onStartup方法
}
}
}
SpringServletContainerInitializer.onStartup()方法的主要逻辑是根据传入的WebApplicationInitializer实现,在过滤掉接口、抽象类与非WebApplicationInitializer实现之后,实例化所有符合要求的WebApplicationInitializer实现。然后对WebApplicationInitializer实现进行排序,接着依次调用WebApplicationInitializer.onStartup方法。
1.注册ContextLoaderListener并初始化ROOT Web应用上下文
根据上面的分析,应用启动过程中,最终会调用到MySpringMvcInitializer类的onStartup方法。先看下MySpringMvcInitializer类的类继承图:
因此,在调用MySpringMvcInitializer.onStartup方法时,会调用到父类AbstractDispatcherServletInitializer.onStratup方法:
// AbstractDispatcherServletInitializer类
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
AbstractDispatcherServletInitializer.onStratup方法首先调用父类AbstractContextLoaderInitializer类的onStartup方法。
// AbstractContextLoaderInitializer类
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
然后AbstractContextLoaderInitializer.onStartup方法调用其registerContextLoaderListener方法向ServletContext添加监听器ContextLoaderListener。
// AbstractContextLoaderInitializer类
protected void c(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");
}
}
registerContextLoaderListener方法中首先调用createRootApplicationContext方法创建ROOT WebApplicationContext:
// AbstractAnnotationConfigDispatcherServletInitializer类
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(configClasses);
return context;
}
else {
return null;
}
}
// MySpringMvcInitializer类
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class}; // ROOT WebApplicationContext
}
createRootApplicationContext方法由子类AbstractAnnotationConfigDispatcherServletInitializer实现,AbstractAnnotationConfigDispatcherServletInitializer.createRootApplicationContext方法最终获取到RootConfig配置类,并创建AnnotationConfigWebApplicationContext实例。
AbstractContextLoaderInitializer.registerContextLoaderListener方法中,在创建了ROOT WebApplicationContext之后,接着实例化ContextLoaderListener,并添加到ServletContext中。由于ContextLoaderListener实现了ServletContextListener接口,因此应用启动过程中,容器会回调ContextLoaderListener.contextInitialized方法,进行ROOT WebApplicationContext的初始化。该过程与Spring MVC XML配置下的ROOT WebApplicationContext初始化流程类似,可参考Spring MVC启动流程分析(xml配置)。
2.注册DispatcherServlet并初始化Web应用上下文
回到AbstractDispatcherServletInitializer.onStartup方法:
// AbstractDispatcherServletInitializer类
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
在调用父类AbstractContextLoaderInitializer类的onStartup方法向ServletContext添加ContextLoaderListener实例、创建并初始化ROOT WebApplicationContext之后,会调用registerDispatcherServlet方法向ServletContext添加DispatcherServlet实例。
// AbstractDispatcherServletInitializer类
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
if (registration == null) {
throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
"Check if there is another servlet registered under the same name.");
}
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
registerDispatcherServlet方法首先调用createServletApplicationContext方法创建DispatcherServlet对应的WebApplicationContext,该方法由子类AbstractAnnotationConfigDispatcherServletInitializer实现:
// AbstractAnnotationConfigDispatcherServletInitializer
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); // 实例化web应用上下文
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
context.register(configClasses);
}
return context;
}
// MySpringMvcInitializer类
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class}; // DispatcherServlet WebApplicationContext
}
registerDispatcherServlet方法然后创建DispatcherServlet实例并注册到ServletContext:
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
registration.setLoadOnStartup(1); // 应用启动时初始化
registration.addMapping(getServletMappings()); // 设置mapping
registration.setAsyncSupported(isAsyncSupported()); // 异步设置
由于这里的DispatcherServlet配置为容器启动时直接初始化,因此该DispatcherServlet会经历XML配置的DispatcherServlet类似的初始化流程,可参考Spring MVC启动流程分析(xml配置)。这里DispatcherServlet初始化过程中会刷新上面已创建的WebApplicationContext,此时WebConfig类中的@EnableWebMvc注解起作用,会注册HandlerMapping、HandlerAdapter等Web组件,覆盖(优先于)DispatcherServlet.initStrategies方法中注册默认Web组件的逻辑;同时WebConfig类中的@ComponentScan注解会起作用,扫描指定路径下的Spring组件(如Controller)。
registerDispatcherServlet方法接着向ServletContext注册Filter:
Filter[] filters = getServletFilters()
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) {
String filterName = Conventions.getVariableName(filter);
Dynamic registration = servletContext.addFilter(filterName, filter);
if (registration == null) {
int counter = 0;
while (registration == null) {
if (counter == 100) {
throw new IllegalStateException("Failed to register filter with name '" + filterName + "'. " +
"Check if there is another filter registered under the same name.");
}
registration = servletContext.addFilter(filterName + "#" + counter, filter);
counter++;
}
}
registration.setAsyncSupported(isAsyncSupported());
registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName());
return registration;
}
private EnumSet<DispatcherType> getDispatcherTypes() {
return (isAsyncSupported() ?
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC) :
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE));
}
三、总结
至此,在应用启动过程中,向web容器ServletContext添加ContextLoaderListener、DispactherServlet、Filter的流程分析结束。可以看到,以上分析的内容基本与在web.xml中配置ContextLoaderListener、DispactherServlet、Filter的作用类似。只不过在Spring MVC Java Config下,利用了ServletContainerInitializer接口和WebApplicationInitializer接口完全去掉了web.xml和Spring XML配置,基于代码的方式配置Spring MVC,初始化ROOT和DispatcherServlet对应的WebApplicationContext。