从Spring源码看创建对象的过程

从Spring源码看创建对象的过程

Spring对于程序员set注入的属性叫做属性的填充、对于set注入之后的处理(包括BeanPostProcessor的处理、初始化方法的处理)叫做初始化。

研读AbstractBeanFactory类中的doGetBean()方法

doGetBean()方法首先完成的工作是获取对象(针对于 scope=singleton 这种形式的对象,Spring把曾经创建获得对象进行存储。后续先获取对象) ,如果获取不到,才会创建对象。

源码图如下:

粗略看创建对象的大体流程

从代码中,我们可以看到一行注释:

java 复制代码
Create bean instance.

这是通过scope进行讨论,以单例情况为例:

  1. 调用AbstractAutowireCapableBeanFactory类中的

    java 复制代码
    protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)方法
  2. createBean方法又会调用本类中的doCreateBean方法;

  3. doCreateBean方法中createBeanInstance就是使用反射进行对象的创建;

  4. populateBean来完成属性填充

    markdown 复制代码
    包括set注入
    和自动注入
        <bean autowired="byName|byType"
        <beans default-autowried 
        @Autowired
  5. initializeBean方法进行初始化操作。

详细阅读doGetBean方法

首先来看一下两个参数

java 复制代码
protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException
name就是bean的id值;
requiredType指创建bean的类型;

代码详解:

markdown 复制代码
final String beanName = transformedBeanName(name);
大多数情况下name和beanName是一样的,就是bean的id值;
但是还有两种情况,需要转换;
    1.实现FactoryBean接口类bean的id;
    比如 beanFactory.getBean("&s") 会把&去掉,剩下s;此时name为&s,beanName被赋值为了s
    2.如果配置文件bean为<bean id="user" name="u"  class="xxxx" />
    beanFactory.getBean("u"),通过别名u来获取bean,会转换成id。
markdown 复制代码
Object bean;
代表最终创建好的对象
markdown 复制代码
Object sharedInstance = getSingleton(beanName);
会从DefaultSingletonBeanRegistry 类中的 singletonObjects、earlySingletonObjects 、singletonFactories获取(从这三个中获取,是为了解决循环引用的问题);
	
1.第一次获取,会获取为null;
2.后续从Spring容器获取,可以获取到:
注意:Spring创建对象对象会有2种状态
    1 完全状态: 对象创建完成 属性填充完成 初始化完成 (如果需要代理 AOP,也已经完成)  
    2 正在创建中: 仅仅有一个简单对象

doGetBean中调用的DefaultSingletonBeanRegistry中getSingleton方法定义如下:

markdown 复制代码
if(sharedInstance != null && args == null) {
    if(logger.isTraceEnabled()) {
        if(isSingletonCurrentlyInCreation(beanName)) {------------------------------判断获取到的bean是否在创建中
            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 + "'");
        }
    }------------------------这些代码是帮Spring开发人员作代码跟踪的
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}

bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);分析

markdown 复制代码
sharedInstance是从getSingleton()方法中获取到的对象,它与返回的bean有什么区别,就是这行代码的主要功能。

applicationContext.xml配置文件中
如果bean的配置为:
<bean id="u" class="xxxx.User"/> 
那么获取到的sharedInstance 就等同于 bean;

如果bean的配置为:
<bean id="fb" class="xxxxx.XXXFactoryBean"/> 
此时获取到的sharedInstance是XXXFactoryBean类的对象,我们想获取的是XXXFactoryBean的getObject()里的对象,此时getObjectForBeanInstance(sharedInstance, name, beanName, null)返回返回 FactoryBean#getObject();

所以:
getObjectForBeanInstance(sharedInstance, name, beanName, null);
如果是简单对象 bean就是sharedInstance 
如果是FactoryBean对象 bean是 FactoryBean#getObject()返回的对象
markdown 复制代码
if(isPrototypeCurrentlyInCreation(beanName)) {
    throw new BeanCurrentlyInCreationException(beanName);
}
用来校验:如果已经在创建这个bean了,抛出异常
markdown 复制代码
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.
        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);
    }
}
======doGetBean源码结束
用来解决Spring的父子容器问题:
    父子容器代码演示
 
    //applicationContext-parents.xml中有配置bean,
    <bean id="p" class="com.sjdwz.Product"/>
    applicationContext.xml中有配置bean
    <bean id="u"  class="com.sjdwz.User"/>
    @Test
    public void test3() {
        DefaultListableBeanFactory parent = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(parent);
        xmlBeanDefinitionReader.loadBeanDefinitions(new ClassPathResource("applicationContext-parents.xml"));
        DefaultListableBeanFactory child = new DefaultListableBeanFactory(parent);//把父容器传进来,作联系
        XmlBeanDefinitionReader xmlBeanDefinitionReader1 = new XmlBeanDefinitionReader(child);
        xmlBeanDefinitionReader1.loadBeanDefinitions(new ClassPathResource("applicationContext.xml"));
        //一旦使用父子容器 最终父子容器的配置信息 融合
        // 如果遇到同名的配置内容 使用子容器中的内容
        User u = (User) child.getBean("u");
        System.out.println("u = " + u);
        Product p = child.getBean("p", Product.class);
        System.out.println("p = " + p);
    }
    注意:如果子容器和父容器有相同类的注入,并且id也相同,会用子容器里的配置。
	如果有父容器有bean的配置信息,在子容器中没有,那么实例化父容器对应的bean(递归进行)
markdown 复制代码
if(!typeCheckOnly) {
    markBeanAsCreated(beanName);
}

    typeCheckOnly是doGetBean()方法的参数,在调用getBean时,设置的值为false;
    typeCheckOnly 标志 false 【默认】
    typeCheckOnly=true代表 spring只是对获取类型进行判断而并不是创建对象
    beanFactory.getBean("u",User.class)
    typeCheckOnly = true:spring是不会创建User对象 判断当前工厂获得或者创建的对象类型是不是User类型
    typeCheckOnly=false:spring会创建对象 或者 获得这个对象 返回给调用者


	markBeanAsCreated(beanName);标志这个bean时需要常见对象,而不是进行类型检查;
	他需要做两件事:
		1. 标记这个bean被创建
		2. clearMergedBeanDefinition(beanName);

markBeanAsCreated(beanName)源码:

markdown 复制代码
要想理解markBeanAsCreated中的clearMergedBeanDefinition()概念,要继续往后看doGetBean()源码。
markdown 复制代码
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);

Spring中bean是有继承关系的。
<bean id="p" abstract=true"/>------------如果一个bean abstract被设置为true,那它就是专门被用来继承的抽象bean,作为父bean
<bean id="u" class="xxx.xxx" parent="p"/>------------parent="p" 指定其继承于哪个bean。
进行汇总 汇总成了 RootBeanDefinition

Spring这样设计,是为了将共有的东西进行抽取到父bean中。
mergedBeanDefinition是指父子bean的定义整合到一起------------------汇总成RootBeanDefinition。


这样就可以回答上面的问题了:markBeanAsCreated()中要标记它被创建完成,就意味着它合并过了,所以要进行clearMergedBeanDefinition()操作。
markdown 复制代码
checkMergedBeanDefinition(mbd, beanName, args);
防止工厂中bean都是抽象bean。
markdown 复制代码
String[] dependsOn = mbd.getDependsOn();
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 + "'");
        }
        registerDependentBean(dep, beanName);
        try {
            getBean(dep);
        } catch(NoSuchBeanDefinitionException ex) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
        }
    }
}

这是为了处理depends-on的(现在已经很少在开发中使用了)
<bean  depends-on=" "/> 

下面就是根据scope进行区分,来创建了:

我们开发中经常使用的是Singleton的scope,所以重点分析它。

markdown 复制代码
if(mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, () - > {
        try {
            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;
        }
    });
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

这是要给lambda表达式:

首先调用getSingleton()方法
getSingleton()方法内部会调用createBean(beanName, mbd, args)方法(这是核心)

getSingleton()方法具体分析

beforeSingletonCreation(beanName)方法分析

markdown 复制代码
用来做校验,需要满足如下两个条件:
1.这个bean没有被排除
	备注:排除的概念,比如@ComponentScan(excludeFilters=)
		比如<context:component-scan base-package="com.sjdwz">
				<context:exclude-filter type="" expression="">
			</context:component-scan>
		用来排除某些bean
2.这个bean此时正在创建中
	如果在bean是被创建中的状态,就没必要再创建了,直接在缓存中获取就可以了,这是为了解决循环依赖问题的。

createBean()方法分析

研读doCreateBean()中创建对象的方法

为什么对返回的是instanceWrapper(bean的包装)呢?

为什么要封装属性呢?便于进行类型转换;

markdown 复制代码
id是Integer类型
比如标签中设置 <property name="id" value="1">
"1"会通过类型转换器转为Integer类型
或者用@PropertySource()中@Value()注解读取propery文件中,也要类型转换器来解决。

类型转换器可以分为以下两种:
1.内置类型转换器,比如:
String转Integer ,String转List,String转数组类型;
2.自定义类型转换器
比如可以自己开发一个String转Date。

类型转换器开发:
1. 使用PropertyEditorSupport,然后注册到 CustomeEditorConfigure 中;这是很早之前流行的做法,现在很少使用了;
2. 实现Converter接口,然后这个bean的名字,必须叫convertionService
markdown 复制代码
createBeanInstance()就是用反射来创建对象了。

doGetBean()方法中对属性进行填充

markdown 复制代码
doGetBean()中populateBean(beanName, mbd, instanceWrapper);是对属性进行填充的。

注入分为,set注入,自动注入(bean或beans标签中autowire属性),构造注入
构造注入是使用构造方法来对属性进行填充的,这应该是由createBeanInstance()方法来调用的,所以不会在中populateBean()体现。
populateBean会进行set注入和自动注入。

set注入分为:
    <bean id="" class="">
    JDK类型  <property name="" value=""/>
    自建类型 <property name="" ref=""/>
    
    
	@Value------JDK类型注入
    @Autowired------自建类型注入    
    @Inject
    @Resources
    
populateBean()方法中hasInstAwareBpps变量来判断是否是注解类型注入;------------------AutowiredAnnotationBeanPostProcessor来处理注解的注入

非注解的属性填充是通过applyPropertyValues(beanName, mbd, bw, pvs);完成的
    
在注入时还会进行类型转换
      如果配置了自定义类型转换器的时候会调用自定义的,否则调用内置类型转换器
      
注意:不管是<bean >标签进行属性填充、还是注解进行属性填充,当这个类型是自定义类型时,都会继续走BeanFactory#getBean()方法

applyPropertyValues()方法说明:

doGetBean()方法中初始化的操作

markdown 复制代码
doGetBean()中exposedObject = initializeBean(beanName, exposedObject, mbd);是进行初始化操作的。

问题:属性填充中的BeanPostProcessor和初始化中的BeanPostProcessor有什么区别呢?

属性填充中的BeanPostProcessor,一般情况下指的是Spring自己提供的BeanPostProcessor;

初始化中的BeanPostProcessor,一般情况下指的是程序员提供的BeanPostProcessor。

下面我们再来讨论下创建对象时,为其做AOP代理是如何进行的。

是在初始化中的BeanPostProcessor的after中进行的。

相关推荐
刘大辉在路上2 小时前
突发!!!GitLab停止为中国大陆、港澳地区提供服务,60天内需迁移账号否则将被删除
git·后端·gitlab·版本管理·源代码管理
追逐时光者4 小时前
免费、简单、直观的数据库设计工具和 SQL 生成器
后端·mysql
初晴~4 小时前
【Redis分布式锁】高并发场景下秒杀业务的实现思路(集群模式)
java·数据库·redis·分布式·后端·spring·
盖世英雄酱581364 小时前
InnoDB 的页分裂和页合并
数据库·后端
小_太_阳5 小时前
Scala_【2】变量和数据类型
开发语言·后端·scala·intellij-idea
直裾5 小时前
scala借阅图书保存记录(三)
开发语言·后端·scala
星就前端叭6 小时前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc
小林coding7 小时前
阿里云 Java 后端一面,什么难度?
java·后端·mysql·spring·阿里云
AI理性派思考者7 小时前
【保姆教程】手把手教你在Linux系统搭建早期alpha项目cysic的验证者&证明者
后端·github·gpu