Spring源码学习笔记——Bean加载

Posted 兴趣使然的草帽路飞

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring源码学习笔记——Bean加载相关的知识,希望对你有一定的参考价值。

在分析源码时,默认大家已经掌握Spring框架的基本使用!如果还不会Spring的萌新,这里推荐几个Spring框架入门的教程:

学完框架,自己多练习使用,只有熟悉使用了,看源码才不那么迷茫!切不可一味的堆积课程进度快餐式学习,要反复消化哦~

下面进入正题:

在这里插入图片描述


Spring源码分析——Bean的加载

0. 前言引入

熟悉Spring框架的小伙伴都知道,Spring有两大核心模块:IOC (控制反转 ) 和 AOP (面向切面编程)。对于Spring IOC,我们又通常将其称为 IOC 容器,IOC 的2个实现方式分别为依赖注入(DI)和依赖查找(DL)。

由于依赖查找(DL)使用的很少,因此 IOC 也被叫做依赖注入

IOCDIDL 的关系图:

在这里插入图片描述

Spring IOC 实现了依赖注入,通过一个核心的 Bean 工厂 (BeanFactory) 来负责各个 Bean 的实例化和依赖管理。各个 Bean 不需要考虑各自复杂的创建过程,进而实现解耦。

对于 IOC 来说,最重要的概念就是容器。容器管理着 Bean 的生命周期,控制着 Bean 的依赖注入。

Spring 作者 Rod Johnson 设计了两个接口用以表示容器:

  • BeanFactory
    • BeanFactory 粗暴简单,可以理解为就是个 HashMap结构,KeyBeanNameValueBean 实例。通常只提供注册(put),获取(get)这两个功能。我们可以称之为 “低级容器”。
  • ApplicationContext
    • ApplicationContext 可以称之为 “高级容器”。因为他比 BeanFactory 多了更多的功能。他继承了多个接口。因此具备了更多的功能。例如资源的获取,支持多种消息(例如 JSP tag 的支持),对 BeanFactory 多了工具级别的支持等待。所以你看他的名字,已经不是 BeanFactory 之类的工厂了,而是 “应用上下文”, 代表着整个大容器的所有功能。该接口定义了一个 refresh 方法,此方法是所有阅读 Spring 源码的人的最熟悉的方法,用于刷新整个容器,即重新加载/刷新所有的 Bean。

我们通过UML图来看一下BeanFactoryApplicationContext的关系:

在这里插入图片描述

从UML关系图中,也可以看出ApplicationContext肯定会比BeanFactory更加复杂。在之后 Spring源码分析——容器扩展中会详细分析(努力更新中)~

如果对Spring IOC的功能进行粗略概括的话,其主要分为如下2个功能点:

  • XML标签的解析与加载(也可以是注解),读取 XML 配置文件,将XML配置文件中的标签信息解析为 BeanDefinition 的形式(即,从配置文件或者注解中获取 Bean 的定义信息解析为 BeanDefinition 对象,并为其注册一些扩展功能)。
    • <bean></bean> —> BeanDefinition :XML标签解析,总体来说主要就是完成对默认标签的4个标签进行解析,即:<import>标签<alias>标签<bean>标签(最为复杂)<beans>标签。其中的过程弯弯绕绕,不过我们只要清楚,目的是将XML 配置文件中的配置转换为 BeanDefinition 对象。
    • @Bean —> BeanDefinition
  • Bean加载,通过XML解析后得到的Bean 的定义信息( BeanDefinition )获取 Bean 对象实例。
    • BeanDefinition —> Bean对象
  • 此外,IOC 还具有:自动装配支持集合指定初始化方法和销毁方法等功能。

如下图所示:

在这里插入图片描述

本文主要是对Bean加载这一流程进行解析,而XML标签的解析加载,这里不作为重点!


1. FactoryBean接口和BeanFactory接口

我们先来分析一下在Spring Bean加载中,常用的2个工厂接口:

  • BeanFactory 是 Bean 的工厂,使用简单工厂模式, ApplicationContext 的父类,IOC 容器的核心(容器顶级接口),负责生产、实例化、配置Bean对象以及建立这些Bean对象间的依赖。BeanFactory 实例化后并不会自动实例化 Bean,只有当 Bean 被使用时才实例化与装配依赖关系,属于延迟加载,适合多例模式。
  • FactoryBean 是 Bean(工厂 Bean),使用了工厂方法模式,作用是生产其他 Bean 实例,可以通过实现该接口,提供一个工厂方法来自定义实例化 Bean 的逻辑。FactoryBean 接口由 BeanFactory 中配置的对象实现,这些对象本身就是用于创建对象的工厂,如果一个 Bean 实现了这个接口,那么它就是创建对象的工厂 Bean,而不是 Bean 实例本身

1.1 FactoryBean接口

FactoryBean接口中定义了三个方法:

public interface FactoryBean<T> {
    // 获取由FactoryBean创建的Bean实例:
   	@Nullable
   	T getObject() throws Exception;

    // 获得FactoryBean创建的Bean的类型:
   	@Nullable
   	Class<?> getObjectType();

    // 判断Bean实例的作用域是否是单例Singleton,是则返回true,否则返回false
    // 如果该实例是单例模式的,则该Bean实例会放到Spring容器的单例缓存池中:
   	default boolean isSingleton() {
      	return true;
   	}
}

1.2 BeanFactory接口

BeanFactory接口中定义了如下一些方法:

public interface BeanFactory {
    // 如果Bean实例对象对应的beanName开头带有&符号,说明是一个FactoryBean类型的对象
    String FACTORY_BEAN_PREFIX = "&";
    
    // 根据beanName从BeanFactory中获取Bean对象
    Object getBean(String name) throws BeansException;
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;
    Object getBean(String name, Object... args) throws BeansException;
    
    // 从BeanFactory中获取指定类型(requiredType)的Bean对象
    <T> T getBean(Class<T> requiredType) throws BeansException;
    <T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
    
    
    <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
    
    <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
    
    // 判断BeanFactory中是否包含指定beanName的Bean对象
    boolean containsBean(String name);
    
    // 当前Bean对象是否是单例Singleton
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
    
    // 当前Bean对象是否是Prototyp模式
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
    
    boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
    
    oolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
    
    // 根据beanName获取Bean对象的类型
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;
    Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;
    
    // 获取指定Bean的别名列表
    String[] getAliases(String name);
}

1.3 基本使用案例

首先新建一个实体类Student:

public class Student {

   private int age;
   private String name;
   private String school;

   // setter/getter方法省略
}

下面新建一个用来作为Bean对象的类,令其实现 FactoryBean 接口:

public class StudentFactoryBean implements FactoryBean<Student> {

   private String studentInfo;

   public String getStudentInfo() {
      return studentInfo;
   }

   public void setStudentInfo(String studentInfo) {
      this.studentInfo = studentInfo;
   }

   @Override
   public Student getObject() throws Exception {
      Student stu = new Student();
      String[] splitInfo = studentInfo.split(",");
      stu.setAge(Integer.valueOf(splitInfo[0]));
      stu.setName(splitInfo[1]);
      stu.setSchool(splitInfo[2]);
      return stu;
   }

   @Override
   public Class<Student> getObjectType() {
      return Student.class;
   }

   @Override
   public boolean isSingleton() {
      return false;
   }
}

上面代码中,最核心的就是T getObject() 方法的重写!下面我们在XML配置文件中将其注入Spring容器:

<bean id="studentFactoryBean" class="top.csp1999.StudentFactoryBean">
   <property name="studentInfo" value="18,csp,Haust" />
</bean>

当XML配置文件中 class 属性配置的实现类是 FactoryBean 接口实现类时,通过 T getBean() 方法返回的不是 FactoryBean 本身,而是 FactoryBeanT getObject() 方法返回的对象。

StudentFactoryBean对象被注入容器中后,Spring会通过反射的方式,发现该对象实现了FactoryBean 接口,那么在调用该Bean对象实例时,Spring容器会自动调用StudentFactoryBean.getObject()方法返回一个 Student 实例对象。

如果我们想要获取的是StudentFactoryBean这个实例工厂本身,而不是其内部的具体对象,那么在使用 getBean(beanName) 方法时在 beanName 前显式的加上 “&” 前缀,例如 getBean("&studentFactoryBean") 即可。代码如下:

// 解析XML获取BeanFactory对象
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring-test.xml"));
// 从BeanFactory中获取FactoryBean实例
StudentFactoryBean studentFactoryBean = (StudentFactoryBean) beanFactory.getBean("&studentFactoryBean");

2. Bean加载总体流程概述

先来看一个Demo:

声明2个要注册到IOC中的对象ComponentAComponentB

public class ComponentA {
}
public class ComponentB {
}

测试从IOC中获取这两个Bean对象:

public class BeanFactoryTest {
	public static void main(String[] args) {

		// 加载与解析XML配置文件,获得BeanFactory:
		BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring-bf.xml"));

		// 从BeanFactory中获取Bean对象
		Object a = beanFactory.getBean("componentA");
		Object b = beanFactory.getBean("componentB");

		System.out.println(a);// com.myspring.test.xmltest.ComponentA@1c93084c
		System.out.println(b);// com.myspring.test.xmltest.ComponentB@6ef888f6
	}
}

由上面的Demo我们可以知道,Spring通过调用 BeanFactorygetBean() 方法来加载 Bean,那么我们进入 AbstractBeanFactory 来看一下源码:

/**
 * 根据参数name,requiredType,args获取Bean对象实例:
 *
 * @param name Bean对象的名称 -> 根据name参数获取对应的Bean对象
 * @param requiredType 检索所需的Bean类型 -> 获取Bean对象时,不仅要根据name去检索,还要判断Bean的类型是否一致
 * @param args 这个参数用到再做分析
 * @return 该方法返回目标Bean的一个实例
 * @throws BeansException if the bean could not be created
 */
public <T> T getBean(String name, @Nullable Class<T> requiredType, @Nullable Object... args)
		throws BeansException {
	// 调用真正去获取Bean对象的方法:
	// 注:Spring框架源码的命名规范非常严谨,doXxx方法(内层方法)内封装的是具体执行逻辑的代码,而调用doXxx的方法是其外层方法
	return doGetBean(name, requiredType, args, false);
}

/**
 * 真正去获取Bean对象的方法:
 * @param name Bean对象的名称 -> 根据name参数获取对应的Bean对象
 * @param requiredType 检索所需的Bean类型
 * @param args 使用显式参数创建bean实例时要使用的参数(仅在创建新实例而不是检索现有实例时才应用)
 * @param typeCheckOnly 是否获取实例用于类型检查而不是实际使用
 * @return 返回Bean的一个实例
 * @throws BeansException if the bean could not be created
 */
@SuppressWarnings("unchecked")
protected <T> T doGetBean(
		String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
		throws BeansException {
	// transformedBeanName(name)方法:根据传入的name参数,获取真正的Bean对应的beanName,什么意思呢?
	// Spring中管理的Beand对象是可以指定设置别名的,Spring Bean设置别名的两种方式:参考 https://blog.csdn.net/qq_34129814/article/details/7
	// 参数传进来的name,有可能是一个别名(eg: alias设置的别名),也有可能是一个&开头的name(解释如下):
	// (1)别名name(eg: alias设置的别名),transformedBeanName(name)方法就是通过别名重定向出来真实beanName名称
	// (2)&开头的name,说明,你要获取的Bean实例对象,是一个FactoryBean对象。
	// FactoryBean:如果某个Bean的配置非常复杂,使用Spring管理不容易、不够灵活,想要使用编码的形式去构建它,
	// 那么你就可以提供一个构建该Bean实例的工厂,这个工厂就是FactoryBean接口实现类。FactoryBean接口实现类还是需要使用Spring管理的。
	// 这里就涉及到两种对象,一种是FactoryBean接口实现类(IOC管理的),另一个就是FactoryBean接口内部管理的对象。
	// 如果要拿FactoryBean接口实现类,使用getBean时传的beanName需要带“&”开头。
	// 如果你要FactoryBean内部管理的对象,你直接传beanName不需要带“&”开头。
	String beanName = transformedBeanName(name);
	// 用于保留返回值(要返回的Bean实例对象)
	Object beanInstance;
	// 根据transformedBeanName方法转换后的真实beanName,直接尝试从缓存中获取Bean的共享单实例(单例):
	// 注:
	// 第一个getSingleton(String beanName)方法是一个参数的,
	// 后面还有一个重载的getSingleton方法(2个参数),2个不要搞混了!
	Object sharedInstance = getSingleton(beanName);
	// CASE1:
	// 如果缓存中有对应的数据,此时缓存数据可能是普通单实例,也可能是 FactoryBean,所以需要根据name来进行判断
	if (sharedInstance != null && args == null) {
		if (logger.isTraceEnabled()) {
			if (isSingletonCurrentlyInCreation(beanName)) {
				logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
						"' that is not fully initialized yet - a consequence of a circular reference");
			}
			else {
				logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
			}
		}
		// 这里为什么又要套呢?为啥不直接拿回去用呢?
		// 其实,你从IOC中拿到的对象,它可能是普通单实例,也可能是FactoryBean实例。
		// 如果是FactoryBean实例,这个时候还要进行处理。主要是看name是带“&” 还是不带“&”,
		// 带“&”:则说明这次getBean方法想要拿FactoryBean对象。
		// 不带“&”:则说明是要拿FactoryBean内部管理的实例。
		/**
		 * 获取给定Bean实例的对象,如果是FactoryBean类型,则可以是该实例本身或其创建的子Bean对象。
		 * 方法参数:
		 * sharedInstance: 缓存中拿到的单实例对象
		 * name: 未处理“&”的name
		 * beanName: 处理过“&”和别名后的name
		 * mbd: 合并过后的bd(BeanDefinition)信息。
		 */
		beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
	}
	// CASE2:
	// 如果根据beanName从缓存中没有找到对应的数据,那么我们就需要自己创建了...
	else {
		// 一、原型循环依赖问题判定:创建Bean时,判断是否出现循环依赖的情况
		// 举个例子:
		// prototypeA -> B, B -> prototypeA
		// 1.会向正在创建中的原型集合内添加一个字符串 “A”
		// 2.创建prototypeA对象,只是一个早期对象。
		// 3.处理prototypeA的依赖,发现A依赖了B类型的对象
		// 4.触发了Spring.getBean(“B”)的操作。
		// 5.根据B的构造方法反射创建出来了B的早期实例
		// 6.Spring处理B对象的依赖,发现依赖了A。
		// 7.Spring转头回来再次去获取A去了。getBean(“A”).
		// 8.条件会返回true,最终抛出异常,算是结束了循环依赖注入。
		if (isPrototypeCurrentlyInCreation(beanName)) {
			throw new BeanCurrentlyInCreationException(beanName);
		}
		// Check if bean definition exists in this factory.
		// 如果beanDefinitionMap中也就是已经加载的类中不包括beanName则尝试从parentBeanFactory中检测
		BeanFactory parentBeanFactory = getParentBeanFactory();
		if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
			// Not found -> check parent.
			String nameToLookup = originalBeanName(name);
			if (parentBeanFactory instanceof AbstractBeanFactory) {
				return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
						nameToLookup, requiredType, args, typeCheckOnly);
			}
			else if (args != null) {
				// Delegation to parent with explicit args.
				// 递归到BeanFactory中寻找
				return (T) parentBeanFactory.getBean(nameToLookup, args);
			}
			else if (requiredType != null) {
				// No args -> delegate to standard getBean method.
				return parentBeanFactory.getBean(nameToLookup, requiredType);
			}
			else {
				return (T) parentBeanFactory.getBean(nameToLookup);
			}
		}
		// 如果不仅仅是做类型检查则是创建Bean,这里要进行记录
		if (!typeCheckOnly) {
			markBeanAsCreated(beanName);
		}
		StartupStep beanCreation = this.applicationStartup.start("spring.beans.instantiate")
				.tag("beanName", name);
		try {
			if (requiredType != null) {
				beanCreation.tag("beanType", requiredType::toString);
			}
			// 二、获取合并BD信息
			// 将存储XML配置文件的GenericBeanDefinition转换为RootBeanDefinition,
			// 如果指定BeanName是子Bean的话同时会合并父类的相关属性
			// 为什么需要合并呀?因为BD支持继承
			// BD: 在XML配置文件中用 parent 属性可以定义父 <bean> 和子 <bean> ,父 <bean> 用 RootBeanDefinition表示
			RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
			// 判断当前BD是否为抽象BD,抽象BD不能创建实例,只能作为父BD让子BD去继承。
			checkMergedBeanDefinition(mbd, beanName, args);
			// 三、depends-on属性处理..
			// <bean name="A" depends-on="B" ... />
			// <bean name="B" .../>
			// 循环依赖问题
			// <bean name="A" depends-on="B" ... />
			// <bean name="B" depends-on="A" .../>
			// Spring是处理不了这种情况的,需要报错..
			// Spring需要发现这种情况的产生。
			// 怎么发现呢? 依靠两个Map,一个map是 dependentBeanMap 另一个是 dependenciesForBeanMap
			// 1. dependentBeanMap 记录依赖当前beanName的其他beanName
			// 2. dependenciesForBeanMap 记录当前beanName依赖的其它beanName集合
			String[] dependsOn = mbd.getDependsOn();
			// 若存在依赖则需要递归实例化依赖的bean
			if (dependsOn != null) {
				for (String dep : dependsOn) {
					// 判断循环依赖..
					if (isDependent(beanName, dep)) {
						throw new BeanCreationException(mbd.getResourceDescription(), beanName,
								"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'
					}
					// 假设<bean name="A" depends-on="B" ... />
					// dep:B,beanName:A
					// 以B为视角 dependentBeanMap {"B":{"A"}}
					// 以A为视角 dependenciesForBeanMap {"A" :{"B"}}
					// 缓存依赖调用
					registerDependentBean(dep, beanName);
					try {
						getBean(dep);
					}
					catch (NoSuchBeanDefinitionException ex) {
						throw new BeanCreationException(mbd.getResourceDescription(), beanName,
								"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
					}
				}
			}
			// CASE-SINGLETON:Create bean instance.
			// 创建单实例的逻辑
			// 实例化依赖的Bean后便可以实例化mbd本身了(singleton模式的创建)
			if (mbd.isSingleton()) {
				// 第二个getSingleton方法,这个方法更倾向于创建实例并返回:
				sharedInstance = getSingleton(beanName, () -> {
					try {
						// 创建单例Bean的核心方法
						return createBean(beanName, mbd, args);
					}
					catch (BeansException ex) {
						// Explicitly remove instance from singleton cache: It might have been put there
						// eagerly by the creation process, to allow for circular reference resolution.
						// Also remove any beans that received a temporary reference to the bean.
						destroySingleton(beanName);
						throw ex;
					}
				});
				//这 里为啥不直接返回,还调用getObjectForBeanInstance(...)?
				// 这里为什么又要套呢?为啥不直接拿回去用呢?
				// 其实,你从IOC中拿到的对象,它可能是普通单实例,也可能是FactoryBean实例。
				// 如果是FactoryBean实例,这个时候还要进行处理。主要是看name是带“&” 还是 不

以上是关于Spring源码学习笔记——Bean加载的主要内容,如果未能解决你的问题,请参考以下文章

源码解析:Spring源码解析笔记启动过程(中)

Spring源码学习bean的加载

9Spring 源码学习 ~ Bean 的加载

10Spring 源码学习 ~ Bean 的加载步骤详解

《spring实战》学习笔记-第二章:装配bean

Spring源码学习~11Bean 的加载步骤详解