spring启动流程 (6完结) springmvc启动流程

SpringMVC的启动入口在SpringServletContainerInitializer类,它是ServletContainerInitializer实现类(Servlet3.0新特性)。在实现方法中使用WebApplicationInitializer创建ApplicationContext、创建注册DispatcherServlet、初始化ApplicationContext等。

SpringMVC已经将大部分的启动逻辑封装在了几个抽象WebApplicationInitializer中,开发者只要继承这些抽象类实现抽象方法即可。

本文将详细分析ServletContainerInitializer、SpringServletContainerInitializer和WebApplicationInitializer的工作流程。

Servlet3.0的ServletContainerInitializer

ServletContainerInitializer接口

ServletContainerInitializer是Servlet3.0的接口。

该接口在web应用程序启动阶段接收通知,注册servlet、filter、listener等。

该接口的实现类可以用HandlesTypes进行标注,并指定一个Class值,后续会将实现、扩展了这个Class的类集合作为参数传递给onStartup方法。

以tomcat为例,在容器配置初始化阶段,将使用SPI查找实现类,在ServletContext启动阶段,初始化并调用onStartup方法来进行ServletContext的初始化。

SpringServletContainerInitializer实现类

在onStartup方法创建所有的WebApplicationInitializer对并调用onStartup方法。

以下为SPI配置,在spring-web/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer文件:

WebApplicationInitializer接口

WebApplicationInitializer接口概述

Interface to be implemented in Servlet 3.0+ environments in order to configure the ServletContext programmatically -- as opposed to (or possibly in conjunction with) the traditional web.xml-based approach.

Implementations of this SPI will be detected automatically by SpringServletContainerInitializer, which itself is bootstrapped automatically by any Servlet 3.0 container. See its Javadoc for details on this bootstrapping mechanism.

示例:

java 复制代码
public class MyWebAppInitializer implements WebApplicationInitializer {

   @Override
   public void onStartup(ServletContext container) {
     // Create the 'root' Spring application context
     AnnotationConfigWebApplicationContext rootContext =
       new AnnotationConfigWebApplicationContext();
     rootContext.register(AppConfig.class);

     // Manage the lifecycle of the root application context
     container.addListener(new ContextLoaderListener(rootContext));

     // Create the dispatcher servlet's Spring application context
     AnnotationConfigWebApplicationContext dispatcherContext =
       new AnnotationConfigWebApplicationContext();
     dispatcherContext.register(DispatcherConfig.class);

     // Register and map the dispatcher servlet
     ServletRegistration.Dynamic dispatcher =
       container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
     dispatcher.setLoadOnStartup(1);
     dispatcher.addMapping("/");
   }
}

开发者可以编写类继承AbstractAnnotationConfigDispatcherServletInitializer抽象类,这个抽象类已经将大部分的初始化逻辑进行了封装。

WebApplicationInitializer实现和继承关系

AbstractContextLoaderInitializer类:

Convenient base class for WebApplicationInitializer implementations that register a ContextLoaderListener in the servlet context. The only method required to be implemented by subclasses is createRootApplicationContext(), which gets invoked from registerContextLoaderListener(ServletContext).

AbstractDispatcherServletInitializer类:

Base class for WebApplicationInitializer implementations that register a DispatcherServlet in the servlet context. Most applications should consider extending the Spring Java config subclass AbstractAnnotationConfigDispatcherServletInitializer.

AbstractAnnotationConfigDispatcherServletInitializer类:

WebApplicationInitializer to register a DispatcherServlet and use Java-based Spring configuration.

Implementations are required to implement:

  • getRootConfigClasses() -- for "root" application context (non-web infrastructure) configuration.
  • getServletConfigClasses() -- for DispatcherServlet application context (Spring MVC infrastructure) configuration.

If an application context hierarchy is not required, applications may return all configuration via getRootConfigClasses() and return null from getServletConfigClasses().

开发者SpringMvcInitializer示例

开发者需要编写类继承AbstractAnnotationConfigDispatcherServletInitializer类,实现几个抽象方法:

java 复制代码
public class SpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

  /**
   * 指定创建root application context需要的@Configuration/@Component类
   */
  @Override
  protected Class<?>[] getRootConfigClasses() {
    return new Class[]{AppConfig.class};
  }

  /**
   * 指定创建Servlet application context需要的@Configuration/@Component类
   * 如果所有的配置类都使用root config classes就返回null
   */
  @Override
  protected Class<?>[] getServletConfigClasses() {
    return null;
  }

  /**
   * Specify the servlet mapping(s) for the DispatcherServlet --- for example "/", "/app", etc.
   */
  @Override
  protected String[] getServletMappings() {
    return new String[]{"/"};
  }
}

SpringMVC启动流程

入口AbstractDispatcherServletInitializer.onStartup方法

java 复制代码
public void onStartup(ServletContext servletContext) throws ServletException {
	super.onStartup(servletContext);
	registerDispatcherServlet(servletContext);
}

父类的onStartup方法创建RootApplicationContext、注册ContextLoaderListener监听器:

java 复制代码
public void onStartup(ServletContext servletContext) throws ServletException {
	registerContextLoaderListener(servletContext);
}

protected void registerContextLoaderListener(ServletContext servletContext) {
	WebApplicationContext rootAppContext = createRootApplicationContext();
	if (rootAppContext != null) {
		// ContextLoaderListener是ServletContextListener
		// 会在contextInitialized阶段初始化RootApplicationContext
		ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
		listener.setContextInitializers(getRootApplicationContextInitializers());
		servletContext.addListener(listener);
	}
}

注册DispatcherServlet

registerDispatcherServlet方法用于创建ServletApplicationContext、注册DispatcherServlet:

java 复制代码
protected void registerDispatcherServlet(ServletContext servletContext) {
	String servletName = getServletName();

	// 创建ServletApplicationContext
	WebApplicationContext servletAppContext = createServletApplicationContext();

	// 创建DispatcherServlet
	FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
	// 添加ApplicationContextInitializer集,会在初始化时调用
	dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

	// 添加到ServletContext
	ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);

	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);
}

触发ContextLoaderListener监听器contextInitialized事件

这个是Servlet的ServletContextListener机制,在ServletContext创建之后触发contextInitialized事件:

java 复制代码
public void contextInitialized(ServletContextEvent event) {
	initWebApplicationContext(event.getServletContext());
}

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
	// 判断是否已经在当前ServletContext绑定了WebApplicationContext
	// 如果已经绑定抛错
	if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
		throw new IllegalStateException("已经初始化过了");
	}

	try {
		if (this.context == null) {
			this.context = createWebApplicationContext(servletContext);
		}
		if (this.context instanceof ConfigurableWebApplicationContext) {
			ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
			if (!cwac.isActive()) {
				if (cwac.getParent() == null) {
					ApplicationContext parent = loadParentContext(servletContext);
					cwac.setParent(parent);
				}
				// refresh
				configureAndRefreshWebApplicationContext(cwac, servletContext);
			}
		}
		servletContext
            .setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

		ClassLoader ccl = Thread.currentThread().getContextClassLoader();
		if (ccl == ContextLoader.class.getClassLoader()) {
			currentContext = this.context;
		} else if (ccl != null) {
			currentContextPerThread.put(ccl, this.context);
		}

		return this.context;
	} catch (RuntimeException | Error ex) {
		servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
		throw ex;
	}
}

protected void configureAndRefreshWebApplicationContext(
		ConfigurableWebApplicationContext wac, ServletContext sc) {
	// 给wac设置id

	wac.setServletContext(sc);
	// 设置spring主配置文件路径
	String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
	if (configLocationParam != null) {
		wac.setConfigLocation(configLocationParam);
	}

	ConfigurableEnvironment env = wac.getEnvironment();
	if (env instanceof ConfigurableWebEnvironment) {
		((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
	}

	customizeContext(sc, wac);
	// 刷新ApplicationContext
	wac.refresh();
}

触发DispatcherServlet的init事件

Servlet在接收请求之前会调用其init方法进行初始化,这个是Servlet的规范。

init方法在其父类HttpServletBean中:

java 复制代码
public final void init() throws ServletException {

	// 从ServletConfig获取配置设置到Servlet
	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) {
			throw ex;
		}
	}

	// 初始化
	initServletBean();
}

// FrameworkServlet
protected final void initServletBean() throws ServletException {
	try {
		// 初始化ServletApplicationContext
		this.webApplicationContext = initWebApplicationContext();
		initFrameworkServlet();
	} catch (ServletException | RuntimeException ex) {
		throw ex;
	}
}

protected WebApplicationContext initWebApplicationContext() {
	// 获取rootApplicationContext
	// 之前的ServletContext初始化阶段已经绑定
	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()) {
				// 将rootApplicationContext设置为Parent
				if (cwac.getParent() == null) {
					cwac.setParent(rootContext);
				}
				// 刷新ApplicationContext
				configureAndRefreshWebApplicationContext(cwac);
			}
		}
	}
	// 如果没有需要查找或创建
	if (wac == null) {
		wac = findWebApplicationContext();
	}
	if (wac == null) {
		wac = createWebApplicationContext(rootContext);
	}

	if (!this.refreshEventReceived) {
		synchronized (this.onRefreshMonitor) {
			// 子类实现
			onRefresh(wac);
		}
	}

	if (this.publishContext) {
		String attrName = getServletContextAttributeName();
		getServletContext().setAttribute(attrName, wac);
	}

	return wac;
}

// DispatcherServlet
protected void onRefresh(ApplicationContext context) {
	initStrategies(context);
}

// 初始化SpringMVC相关组件
protected void initStrategies(ApplicationContext context) {
	initMultipartResolver(context);
	initLocaleResolver(context);
	initThemeResolver(context);
	initHandlerMappings(context);
	initHandlerAdapters(context);
	initHandlerExceptionResolvers(context);
	initRequestToViewNameTranslator(context);
	initViewResolvers(context);
	initFlashMapManager(context);
}

SpringMVC启动流程总结

  • 创建RootApplicationContext、注册ContextLoaderListener监听器
  • 创建ServletApplicationContext、注册DispatcherServlet
  • 触发ContextLoaderListener监听器contextInitialized事件,初始化RootApplicationContext
  • 触发DispatcherServlet的init事件,初始化ServletApplicationContext
相关推荐
露天赏雪23 分钟前
第3篇:Sentinel熔断降级实战:大模型接口抖动时的服务兜底方案
java·开发语言·人工智能·spring boot·spring·servlet·sentinel
天天被压力30 分钟前
【A股分钟级高频数据完全手册 #06】北交所分钟级数据接口:京市股票Pro版最新分时
java·人工智能·python
天天被压力36 分钟前
【A股分钟级高频数据完全手册 #05】指数历史分钟数据接口:Pro版沪深指数分时回放
java·人工智能·python
captain3761 小时前
▲统一功能——DispatcherServlet源码分析
java·spring·java-ee·log4j·maven
金銀銅鐵1 小时前
JDK 21 中 List.of 的实现细节:从 List12 到 ListN
java·后端·ai编程
yeccheng3 小时前
MySQL 索引超详解:从底层 B+ 树到面试高频考点
java·开发语言·jvm·mysql·面试
这个昵称不容易3 小时前
SpringBoot 读书笔记共享平台部署实战:97 个 Java 文件 / 113 个接口 / 10 张表
java·springboot·毕设
金銀銅鐵5 小时前
一个 Java 文件实现《孫子兵法·始計篇》中英对照阅读器
java·后端·ai编程
DevOpenClub5 小时前
高考教育信息问答 API:会话参数、资料引用与流式响应
java·数据库·高考
bro_Java6665 小时前
《排序的底层逻辑:比较、交换与分治》
java·算法·排序算法