520就应该和女朋友一起学习Spring源码——Bean加载
Posted 兴趣使然の草帽路飞
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了520就应该和女朋友一起学习Spring源码——Bean加载相关的知识,希望对你有一定的参考价值。
在分析源码时,默认大家已经掌握Spring框架的基本使用!如果还不会Spring的萌新,这里推荐几个Spring框架入门的教程:
- 快速入门:狂神讲的Spring教程,这个很适合刚入门,想快速过一遍Spring框架的小伙伴!
- 加强巩固:尚硅谷-Spring5框架最新版教程,尚硅谷的教程质量一向很不错,非常全面,也适合初学框架的新手!
学完框架,自己多练习使用,只有熟悉使用了,看源码才不那么迷茫!切不可一味的堆积课程进度快餐式学习,要反复消化哦~
下面进入正题:
- 分析源码离不开注释,先搭建起来Spring源码阅读环境:超详细图解!教小白学妹基于IDEA+Gradle+jdk11搭建Spring框架源码阅读环境
- 本文主要内容参考《Spring源码深度解析》这本书,以及一些技术博客分享。
- Spring源码系列文章肝了整整2个月,希望大家三连支持一下!
Spring源码分析——Bean的加载
0. 前言引入
熟悉Spring框架的小伙伴都知道,Spring有两大核心模块:IOC (控制反转 ) 和 AOP (面向切面编程)。对于Spring IOC,我们又通常将其称为 IOC 容器,IOC 的2个实现方式分别为依赖注入(DI)和依赖查找(DL)。
注:由于依赖查找(DL)使用的很少,因此 IOC 也被叫做依赖注入。
IOC 和 DI 、DL 的关系图:
Spring IOC 实现了依赖注入,通过一个核心的 Bean 工厂 (BeanFactory) 来负责各个 Bean 的实例化和依赖管理。各个 Bean 不需要考虑各自复杂的创建过程,进而实现解耦。
对于 IOC 来说,最重要的概念就是容器。容器管理着 Bean 的生命周期,控制着 Bean 的依赖注入。
Spring 作者 Rod Johnson 设计了两个接口用以表示容器:
BeanFactory
- BeanFactory 粗暴简单,可以理解为就是个 HashMap结构,Key 是
BeanName
,Value 是Bean 实例
。通常只提供注册(put
),获取(get
)这两个功能。我们可以称之为 “低级容器”。
- BeanFactory 粗暴简单,可以理解为就是个 HashMap结构,Key 是
ApplicationContext
- ApplicationContext 可以称之为 “高级容器”。因为他比 BeanFactory 多了更多的功能。他继承了多个接口。因此具备了更多的功能。例如资源的获取,支持多种消息(例如 JSP tag 的支持),对 BeanFactory 多了工具级别的支持等待。所以你看他的名字,已经不是 BeanFactory 之类的工厂了,而是 “应用上下文”, 代表着整个大容器的所有功能。该接口定义了一个
refresh
方法,此方法是所有阅读 Spring 源码的人的最熟悉的方法,用于刷新整个容器,即重新加载/刷新所有的 Bean。
- ApplicationContext 可以称之为 “高级容器”。因为他比 BeanFactory 多了更多的功能。他继承了多个接口。因此具备了更多的功能。例如资源的获取,支持多种消息(例如 JSP tag 的支持),对 BeanFactory 多了工具级别的支持等待。所以你看他的名字,已经不是 BeanFactory 之类的工厂了,而是 “应用上下文”, 代表着整个大容器的所有功能。该接口定义了一个
我们通过UML图来看一下BeanFactory
与ApplicationContext
的关系:
从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
本身,而是 FactoryBean
的 T 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中的对象ComponentA
、ComponentB
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通过调用 BeanFactory
的 getBean()
方法来加载 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是带“&” 还是 不带“&”,
// 带“&”说明这次getBean想要拿FactoryBean对象。
// 否则是要拿FactoryBean内部管理的实例。
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// CASE-PROTOTYPE: 创建多实例
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
// prototype原型模式的创建(new)
Object prototypeInstance = null;
try {
// 记录当前线程相关的正在创建的原型对象beanName
beforePrototypeCreation(beanName);
// createBean方法创建对象
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
// 从正在创建中的集合中移除beanName对应的Bean。
afterPrototypeCreation(beanName);
}
beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
// CASE-OTHER: 这一情况笔记复杂,不做分析!
else {
// 指定的scope上实例化bean
String scopeName = mbd.getScope();
if (!StringUtils.hasLength(scopeName)) {
throw new IllegalStateException("No scope name defined for bean ´" Spring源码解析——Bean加载(doCreateBean方法补充)
呕心沥血整理了~这100款告白源码❤学妹们看呆了~(520/七夕/告白/求婚/脱单)
520前,我放弃陪女朋友时间,被迫写代码:“SSM框架整合+excel文件上传到数据库+数据更新“