目录

  • 前言
  • 版本
  • 源码分析
  • ApplicationContext
  • prepareRefresh
  • obtainFreshBeanFactory
  • prepareBeanFactory
  • postProcessBeanFactory
  • invokeBeanFactoryPostProcessors
  • registerBeanPostProcessors
  • initMessageSource,initApplicationEventMulticaster,onRefresh,registerListeners
  • finishBeanFactoryInitialization
  • 循环依赖
  • finishRefresh


前言

上一章总体介绍了一下Spring以及IoC,本章详解分析下Spring的启动流程。

版本

本文以Spring 5.3.16的代码为例,分析启动流程。

源码分析

在前一章引出了两个问题:

  1. 哪些类需要注册到IoC容器中,由容器负责创建,比如上面的例子,怎么把Company类放入到容器中管理
  2. 类中的哪些成员需要容器自动注入,比如怎么让容器把boss和worker注入到company中

哪些类需要注册到IoC容器中,或者说,如何指定一个类由IoC容器创建和管理。有以下几种方式:

基于XML配置

基于注解配置

基于Java类配置

在XML中通过<bean>来定义

在bean实现类上注解@Component或者衍生注解@Resource,@Controller等

在注解了@Configuration类中,通过在类方法上注解@Bean定义一个bean,方法返回一个bean

Bean实现类来源于第三方类库,如DataSource、JdbcTemplate等,无法在类上注解,通过XML配置比较好;命名空间的配置,aop、context等,只能采用XML

Bean实现类是当前项目开发的,可以直接在Java类上使用注解

Bean的实例化逻辑比较复杂,可以采用这种方式

在Spring和SpringMVC时代用基于XML和基于注解的比较多,在SpringBoot后采用基于注解和基于Java类的比较多,基于XML的越来越少。

ApplicationContext

上一章说到开发时一般不直接用BeanFactory,而是用ApplicationContext,那么基于XML配置和基于注解配置对应的类分别是ClassPathXmlApplicationContext和AnnotationConfigApplicationContext。
我们先使用ClassPathXmlApplicationContext,然后跟踪代码看下Spring是如何启动的。

ApplicationContext context = new ClassPathXmlApplicationContext("server.xml");

在server.xml中定义bean

<?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 http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <bean id="child" class="org.example.ChildClz"></bean>
</beans>

启动之后,我们跟进去看下

public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
	this(new String[] {configLocation}, true, null);
}

public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {

	super(parent);
	setConfigLocations(configLocations);
	if (refresh) {
		refresh();
	}
}

super(parent)主要是设置parent和ResourceResolver,setConfigLocations保存XML路径,主逻辑都在refresh方法里。

public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

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

				StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

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

				// 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();
				contextRefresh.end();
			}
		}
	}

prepareRefresh

prepareRefresh做一些初始化操作,比如设置当前状态this.closed.set(false);this.active.set(true);初始化配置initPropertySources。

obtainFreshBeanFactory

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(),这个方法从名字上就可以看出是获取beanFactory,我们看看代码内部做了什么

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		refreshBeanFactory();
		return getBeanFactory();
}

protected final void refreshBeanFactory() throws BeansException {
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			customizeBeanFactory(beanFactory);
			loadBeanDefinitions(beanFactory);
			this.beanFactory = beanFactory;
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
}

先new了一个beanFactory,然后loadBeanDefinitions,我们用的是ClassPathXmlApplicationContext,所以首先是解析XML的具体路径,把ClassPath转成具体的File路径,然后读取XML并进行解析。
这里是用自带XML解析器来解析,最终交由BeanDefinitionParserDelegate来处理解析出来的元素,其中有parseBeanDefinitionElement,解析<bean>元素;parseCustomElement解析自定义元素,比如<context:component-scan>标签。
我们先看下具体是怎么解析<bean>标签:

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
        //从属性中获取Id
		String id = ele.getAttribute(ID_ATTRIBUTE);
		//从属性中获取name
		String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

		//按,;分割name,生成别名数组
		List<String> aliases = new ArrayList<>();
		if (StringUtils.hasLength(nameAttr)) {
			String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
			aliases.addAll(Arrays.asList(nameArr));
		}

		//beanName等于Id,如果没定义Id,则取别名数组中的第一个作为beanName,剩余的作为别名
		String beanName = id;
		if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
			beanName = aliases.remove(0);
			if (logger.isTraceEnabled()) {
				logger.trace("No XML 'id' specified - using '" + beanName +
						"' as bean name and " + aliases + " as aliases");
			}
		}

		//检查beanName是否重复
		if (containingBean == null) {
			checkNameUniqueness(beanName, aliases, ele);
		}

		//继续解析,生成beanDefinition,如果XML没定义Id,也没定义name,则按规则生成,比如beanClassName#0
		AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
		if (beanDefinition != null) {
			if (!StringUtils.hasText(beanName)) {
				try {
					if (containingBean != null) {
						beanName = BeanDefinitionReaderUtils.generateBeanName(
								beanDefinition, this.readerContext.getRegistry(), true);
					}
					else {
						beanName = this.readerContext.generateBeanName(beanDefinition);
						// Register an alias for the plain bean class name, if still possible,
						// if the generator returned the class name plus a suffix.
						// This is expected for Spring 1.2/2.0 backwards compatibility.
						String beanClassName = beanDefinition.getBeanClassName();
						if (beanClassName != null &&
								beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
								!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
							aliases.add(beanClassName);
						}
					}
					if (logger.isTraceEnabled()) {
						logger.trace("Neither XML 'id' nor 'name' specified - " +
								"using generated bean name [" + beanName + "]");
					}
				}
				catch (Exception ex) {
					error(ex.getMessage(), ele);
					return null;
				}
			}
			String[] aliasesArray = StringUtils.toStringArray(aliases);
			return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
		}

		return null;
}

以上代码,通过解析<bean>标签生成beanDefinition,beanName按以下规则:

  1. 如果在XML的<bean>标签中定义了属性id,那么beanName=id
  2. 如果没有定义属性id,但定义了name,按,;分割name,取第一个作为beanName,剩余的作为别名
  3. 如果没有定义id,也没定义name,那么按一定规则生成beanName,比如beanClassName#0

解析出了beanDefinition,下一步就是注册到beanFactory中:

public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {

		// Register bean definition under primary name.
		String beanName = definitionHolder.getBeanName();
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

		// Register aliases for bean name, if any.
		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
			for (String alias : aliases) {
				registry.registerAlias(beanName, alias);
			}
		}
}

以beanName为key,最终注册到beanFactory中的beanDefinitionMap中。bean的别名也存到beanFactory的aliasMap中。
我们回顾一下,简单来讲,refresh的第二步就是生成beanFactory,在生成beanFactory时解析XML,处理<bean>标签,生成beanDefinition,再以beanName为key,注册到beanFactory的beanDefinitionMap中。这个时候只是保存了bean的定义,还没有实例化bean。

prepareBeanFactory

从名字可以看出是做一些beanFactory的准备工作,这里就不放出详细代码了,主要做了以下工作:

  1. 设置classLoader
  2. 设置bEL(Spring Bean 的 EL 表达式,用于在运行时访问bean属性或方法的语言)
  3. 添加BPP,BeanPostProcessor,用于在bean实例化后做一些操作。这里添加的就是ApplicationContextAwareProcessor,就是处理各种Aware的,把对应的值赋值到bean实例中
  4. 调用beanFactory的registerResolvableDependency方法,注册可解析的依赖项
  5. 调用beanFactory的registerSingleton方法,注册单例

registerResolvableDependency和registerSingleton都是往beanFactory中注册实例,两者主要是使用场景不同。registerResolvableDependency方法注册的是一个可解决的依赖项,而不是一个单例对象。该方法的主要作用是为了解决某些场景下的依赖注入问题,如数据源、配置信息等。当容器在创建bean时需要访问这些资源或对象时,会优先从这些注册的可解决依赖项中查找对应类型的对象并返回。
registerResolvableDependency和registerSingleton往beanFactory中注入一些基础类,比如BeanFactory,Environment,SystemProperties等。

postProcessBeanFactory

在beanFactory加载所有beanDefinition之后,但还没有实例化bean之前做一些事情,比如注册一些BPP,此方法为空。

invokeBeanFactoryPostProcessors

运行BFPP,在beanFactory加载所有beanDefinition之后,但还没有实例化bean之前做一些事情。
在Spring容器启动时,会先进行必要的加载和实例化操作,然后调用invokeBeanFactoryPostProcessors方法来执行所有已经注册的BeanFactoryPostProcessor对象。这些对象的作用是在bean定义被解析之后,在bean实例化之前对bean工厂进行自定义修改或扩展,例如增加或删除某些属性、更改bean的作用域等。
该方法的调用顺序如下:

  1. 调用所有实现了PriorityOrdered接口的BeanFactoryPostProcessor对象的postProcessBeanFactory方法;
  2. 调用所有实现了Ordered接口的BeanFactoryPostProcessor对象的postProcessBeanFactory方法;
  3. 调用所有其他类型的BeanFactoryPostProcessor对象的postProcessBeanFactory方法。

在调用postProcessBeanFactory方法时,会将当前的bean工厂作为参数传入,以便进行定制化的修改。

registerBeanPostProcessors

从beanFactory中查找BPP对象,添加到beanFactory的BPP数组中

initMessageSource,initApplicationEventMulticaster,onRefresh,registerListeners

这部分就不详细介绍了,简单介绍下逻辑:

  1. initMessageSource,初始化消息源
  2. initApplicationEventMulticaster,初始化事件广播器,ApplicationContext是有事件机制的
  3. onRefresh,默认为空,子类扩展用的
  4. registerListeners,注册事件监听器

finishBeanFactoryInitialization

前面说到beanFactory只是加载了所有beanDefinition,还没有实例化,在这一步里实例化所有非懒加载的单例对象。

怎么在启动类使用SpringContextHolder完成init方法_java

这里只描述了主干逻辑,一些细枝末节可以去看源码。主要步骤如下:

  1. 遍历beanFactory中的所有beanName
  2. 如果是FactoryBean,走工厂bean的流程
  3. 如果不存在bean定义,那么去父工厂中查找
  4. 如果不是工厂bean,也有bean定义,那么就开始创建bean
  5. 如果在XML中定义了depends-on,就先实例化depend-on
  6. 从bean定义中找到beanClass,根据参数、策略选择构造函数,创建实例
  7. 向实例注入属性,如果在XML中配置了Autowire,则根据具体的配置,按类型或者按名字自动装配属性
  8. 属性注入后,表明bean已经创建成功,但还没有初始化,在初始化之前,先调用所有BPP的postProcessBeforeInitialization方法
  9. 初始化bean,如果bean实现了InitializingBean或者配置了init方法
  10. 调用所有BPP的 postProcessAfterInitialization方法

这里说的Autowire是指在XML中配置的,与@Autowire注解不一样,注解是通过BPP实现的,在后面会介绍。

循环依赖

在bean实例化过程中,还存在循环依赖问题,比如A依赖B,B依赖A。Spring通过三级缓存来解决循环依赖问题:

怎么在启动类使用SpringContextHolder完成init方法_XML_02


如上图,三级缓存分为:

  1. 一级缓存,里面保存的是成品,是完成创建好的bean实例
  2. 二级缓存,里面保存的是半成品,比如A依赖B这种情况,A已经创建好,但它的成员变量B还没创建好
  3. 三级缓存,里面保存的是创建bean的工厂方法

我们以A依赖B,B依赖A的情况具体分析:

  1. 从一级缓存中获取A,缓存中没有A的实例
  2. A设置正在创建的状态,创建A的实例,并添加到三级缓存中
  3. A的属性注入,由于A依赖B,会去缓存中获取B的实例
  4. 从一级缓存中获取B,一级没有去二级缓存找,二级没有去三级找,缓存中没有B的实例
  5. B设置正在创建的状态,创建B的实例,并添加到三级缓存中
  6. B的属性注入,由于B依赖A,会去缓存中获取A的实例
  7. 从一级缓存中获取A,一级没有去二级缓存找,二级没有去三级找
  8. 因为在2中,已添加A到三级缓存中,则取出A,并添加到二级缓存中
  9. B创建完成后,添加到一级缓存,并删除二级和三级缓存
  10. B创建完成后,流程回到A的属性注入流程,完成属性注入和初始化后,A创建完成,添加到一级缓存,删除二级和三级缓存

二级缓存就能解决循环依赖问题,三级缓存主要是考虑到AOP功能。

不过这种三级缓存的方法也不能解决所有循环依赖问题,一般只能用来解决单例的setter注入。

怎么在启动类使用SpringContextHolder完成init方法_spring_03

finishRefresh

beanFactory实例化所有bean后,整个启动流程就基本结束了,后面的如finishRefresh是做事件发布的,还有缓存清理工作。

以上就是整个启动流程,启动完成后就可以通过getBean这种方法来从beanFactory中来获取bean了。本章只是介绍了主体流程,使用XML配置作为例子。基于注解的方式和基于Java类的f方式,常用注解如@Component和@Autowired的原理将在后面章节具体介绍。