Spring启动流程源码解析

 更新时间:2024年07月18日 14:16:49   作者:谈谈1974  
这篇文章主要介绍了Spring启动流程源码解析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

1. Spring 启动配置

Spring的启动是基于 web 容器的,所有 web工程的初始配置都写在 web.xml 中,该文件一般配置了context 参数,servlet 和监听器(listener)。

< context-param >是初始化 Context 的配置,< listener >调用 Spring 包中的 ContextLoaderListener ,用于监听 web 容器初始化事件,并加载相关配置

<!-- Spring 启动配置文件 -->
    <context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:spring-mybatis.xml</param-value>
	</context-param>
<!-- Spring 启动监听器 -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

<!-- Spring MVC servlet -->
	<servlet>
		<servlet-name>SpringMVC</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:spring-mvc.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
		<async-supported>true</async-supported>
	</servlet>

2. Spring 启动流程

Spring 的启动流程可以分为两部分:

  • 基于 web 容器的全局域 ServletContext 创建 WebApplicationContext作为 RootContext,也就是整个框架的核心容器
  • 配置的其他 Spring servlet 基于 RootContext 创建自己的 WebApplicationContext,从而持有自己的 bean 空间

2.1 Spring 基于 ServletContext 创建 RootContext

Spring 的启动其实就是 IoC 容器的启动过程,其核心监听器 ContextLoaderListener 父类是 ContextLoader,实现了 ServletContextListener 接口,在容器启动时会触发其 contextInitialized 初始化方法

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
 /**
  * Initialize the root web application context.
  */
 @Override
 public void contextInitialized(ServletContextEvent event) {
 	initWebApplicationContext(event.getServletContext());
 }
}

此处 initWebApplicationContext() 是 ContextLoader 中的方法, 该方法用于对 整个Spring 框架的ApplicationContext 进行初始化,在这里进入了spring IoC的初始化。

这个方法主要做了三件事:

  • 【1】createWebApplicationContext()实际创建 XmlWebApplicationContext 作为 RootContext
  • 【2】configureAndRefreshWebApplicationContext()加载 Spring 配置文件中的配置并创建 bean
  • 【3】servletContext.setAttribute()将 WebApplicationContext 放入 ServletContext 全局域
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
   if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
   	throw new IllegalStateException(
   			"Cannot initialize context because there is already a root application context present - " +
   			"check whether you have multiple ContextLoader* definitions in your web.xml!");
   }
   
   ······
   
   try {
   	// Store context in local instance variable, to guarantee that
   	// it is available on ServletContext shutdown.
   	if (this.context == null) {
   		this.context = createWebApplicationContext(servletContext);
   	}
   	if (this.context instanceof ConfigurableWebApplicationContext) {
   		ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
   		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 ->
   				// determine parent for root web application context, if any.
   				ApplicationContext parent = loadParentContext(servletContext);
   				cwac.setParent(parent);
   			}
   			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 ex) {
   	logger.error("Context initialization failed", ex);
   	servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
   	throw ex;
   }
   catch (Error err) {
   	logger.error("Context initialization failed", err);
   	servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
   	throw err;
   }
}

configureAndRefreshWebApplicationContext(cwac, servletContext)会从 web.xml 中读取 contextConfigLocation 配置,也就是spring xml文件配置,将其存入 WebApplicationContext 中,最后调用refresh() 方法执行所有Java对象的创建工作。

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
	if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
		// The application context id is still set to its original default value
		// -> assign a more useful id based on available information
		String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
		if (idParam != null) {
			wac.setId(idParam);
		}
		else {
			// Generate default id...
			wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
					ObjectUtils.getDisplayString(sc.getContextPath()));
		}
	}

	wac.setServletContext(sc);
	// 获取 contextConfigLocation 配置文件
	String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
	if (configLocationParam != null) {
		wac.setConfigLocation(configLocationParam);
	}

	// The wac environment's #initPropertySources will be called in any case when the context
	// is refreshed; do it eagerly here to ensure servlet property sources are in place for
	// use in any post-processing or initialization that occurs below prior to #refresh
	ConfigurableEnvironment env = wac.getEnvironment();
	if (env instanceof ConfigurableWebEnvironment) {
		((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
	}

	customizeContext(sc, wac);
	wac.refresh(); // Spring IOC 创建 Bean
}

refresh() 方法的实现在 AbstractApplicationContext类中,其主要方法功能如图所示。

public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		// Prepare this context for refreshing.
		prepareRefresh();

		// Tell the subclass to refresh the internal bean factory.
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		// Prepare the bean factory for use in this context.
		prepareBeanFactory(beanFactory);

		try {
			// Allows post-processing of the bean factory in context subclasses.
			postProcessBeanFactory(beanFactory);

			// Invoke factory processors registered as beans in the context.
			invokeBeanFactoryPostProcessors(beanFactory);

			// Register bean processors that intercept bean creation.
			registerBeanPostProcessors(beanFactory);

			// Initialize message source for this context.
			initMessageSource();

			// Initialize event multicaster for this context.
			initApplicationEventMulticaster();

			// Initialize other special beans in specific context subclasses.
			onRefresh();

			// Check for listener beans and register them.
			registerListeners();

			// Instantiate all remaining (non-lazy-init) singletons.
			finishBeanFactoryInitialization(beanFactory);

			// Last step: publish corresponding event.
			finishRefresh();
		}

		catch (BeansException ex) {
			if (logger.isWarnEnabled()) {
				logger.warn("Exception encountered during context initialization - " +
						"cancelling refresh attempt: " + ex);
			}

			// Destroy already created singletons to avoid dangling resources.
			destroyBeans();

			// Reset 'active' flag.
			cancelRefresh(ex);

			// Propagate exception to caller.
			throw ex;
		}

		finally {
			// Reset common introspection caches in Spring's core, since we
			// might not ever need metadata for singleton beans anymore...
			resetCommonCaches();
		}
	}
}

2.2 Spring servlet 基于 RootContext 创建 WebApplicationContext

contextLoaderListener 监听器初始化完毕后,开始初始化web.xml中配置的 servlet。

servlet可以配置多个,以最常见的DispatcherServlet为例,DispatcherServlet 在初始化的时候会建立自己的 IoC context,用以持有spring mvc相关的bean。

DispatcherServlet 继承关系如下图,web容器启动时 servlet 的调用链如下:

GenericServlet#init()->HttpServletBean#init()->FrameworkServlet#initServletBean()->initWebApplicationContext()

此处 FrameworkServlet # initWebApplicationContext() 方法与 Spring框架创建 RootContext 流程大致相同,分为以下几步,只不过设置的 parent context 不是 ServletConext 而是 Spring 核心容器 RootContext

  • 【1】WebApplicationContextUtils.getWebApplicationContext获取 RootContext
  • 【2】使用已有或者新建的 WebApplicationContext 加载 SpringMVC 配置文件中的配置并创建 bean
  • 【3】onRefresh()调用 DispatcherServlet#initStrategies() 进行 servlet 初始化
  • 【4】将 servlet 自己的 WebApplicationContext 存入 ServletContext
protected WebApplicationContext initWebApplicationContext() {
		// 获取 root context
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

		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
			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.
			onRefresh(wac);
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
						"' as ServletContext attribute with name [" + attrName + "]");
			}
		}

		return wac;
	}

3. 总结

一个web应用部署在 web 容器中,web容器为其提供一个全局域 ServletContext,作为spring IoC容器 WebApplicationContext 的宿主环境

在web.xml 中配置的 contextLoaderListener 会在容器启动时初始化 一个WebApplicationContext 作为 RootContext。

这是一个接口类,其实际的实现类是 XmlWebApplicationContext (在 ContextLoader# determineContextClass()方法中决定)。

这个就是 spring 的核心 IoC 容器,其对应 bean 定义的配置由web.xml 中的 context-param 标签指定,并通过 refresh()方法完成 bean 创建。

IoC容器初始化完毕后,Spring 将以 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE作为 Key,将创建的 XmlWebApplicationContext 对象存储到 ServletContext 中,便于之后作为 RootContext 使用

protected Class<?> determineContextClass(ServletContext servletContext) {
    // 如果在 web.xml 中直接指定了 ContextClass
 	String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
 	if (contextClassName != null) {
 		try {
 			return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
 		}
 		catch (ClassNotFoundException ex) {
 			throw new ApplicationContextException(
 					"Failed to load custom context class [" + contextClassName + "]", ex);
 		}
 	}
 	else { 
 	// 没有直接指定,则读取属性文件 ContextLoader.properties 的配置
 	/** org.springframework.web.context.WebApplicationContext=
     org.springframework.web.context.support.XmlWebApplicationContext
     */
 		contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
 		try {
 			return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
 		}
 		catch (ClassNotFoundException ex) {
 			throw new ApplicationContextException(
 					"Failed to load default context class [" + contextClassName + "]", ex);
 		}
 	}
 }

contextLoaderListener 监听器初始化完毕后,开始初始化 web.xml 中配置的servlet。

以DispatcherServlet为例,DispatcherServlet 在初始化的时候会建立自己的 context,用以持有spring mvc相关的 bean,并完成 bean 的创建。

初始化时设置其 parent context 为 Spring 的核心容器 RootContext,这样每个 servlet 都拥有自己的 context,即拥有自己独立的bean空间,同时又共享 RootContext 中定义的那些bean。

当 Spring 组件在执行 getBean 时,如果在自己的 context 中找不到对应的bean,则会在父ApplicationContext (通常为Spring 核心容器 RootContext)中去找,这也就解释了在 DispatcherServlet 中为何可以获取到由 ContextLoaderListener 创建的ApplicationContext中的bean。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java使用Statement接口执行SQL语句操作实例分析

    Java使用Statement接口执行SQL语句操作实例分析

    这篇文章主要介绍了Java使用Statement接口执行SQL语句操作,结合实例形式详细分析了Java使用Statement接口针对mysql数据库进行连接与执行SQL语句增删改查等相关操作技巧与注意事项,需要的朋友可以参考下
    2018-07-07
  • JPA之EntityManager踩坑及解决:更改PersistenceContext

    JPA之EntityManager踩坑及解决:更改PersistenceContext

    这篇文章主要介绍了JPA之EntityManager踩坑及解决:更改PersistenceContext方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • Servlet机制Pushlet原理及用法详解

    Servlet机制Pushlet原理及用法详解

    这篇文章主要介绍了Servlet机制Pushlet原理及用法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-10-10
  • Java输入学号、姓名、年龄并对其进行输出的实现方法

    Java输入学号、姓名、年龄并对其进行输出的实现方法

    这篇文章主要给大家介绍了关于Java输入学号、姓名、年龄并对其进行输出的实现方法,在计算机编程中,输出学号和姓名是一个常见的任务,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2023-09-09
  • springMVC中HttpMessageConverter的具体使用

    springMVC中HttpMessageConverter的具体使用

    HttpMessageConverter,报文信息转换器,将请求报文转换为Java对象,本文主要介绍了springMVC中HttpMessageConverter的具体使用,具有一定的参考价值,感兴趣的可以了解一下
    2023-08-08
  • MyBatis使用接口映射的方法步骤

    MyBatis使用接口映射的方法步骤

    映射器是MyBatis中最核心的组件之一,本文主要介绍了MyBatis使用接口映射的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-07-07
  • IntelliJ IDEA窗口组件具体操作方法

    IntelliJ IDEA窗口组件具体操作方法

    IDEA刚接触不久,各种常用工具窗口找不到,不小心关掉不知道从哪里打开,今天小编给大家分享这个问题的解决方法,感兴趣的朋友一起看看吧
    2021-09-09
  • 手把手带你粗略了解Java--类和对象

    手把手带你粗略了解Java--类和对象

    这篇文章主要给大家介绍了关于java中类和对象的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-08-08
  • 常用的java日期比较和日期计算方法小结

    常用的java日期比较和日期计算方法小结

    这篇文章主要为大家详细总结了常用的java日期比较和日期计算方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-09-09
  • 解决idea spring boot 修改html等不重启即时生效的问题

    解决idea spring boot 修改html等不重启即时生效的问题

    这篇文章主要介绍了解决idea spring boot 修改html等不重启即时生效的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02

最新评论