框架源碼系列九:依賴注入DI、三種Bean配置方式的註冊和實例化過程

1、依賴注入DI

學習目標
1)搞清楚構造參數依賴注入的過程及類
2)搞清楚註解方式的屬性依賴注入在哪裏完成的。
學習思路
1)思考咱們手寫時是如何作的
2)讀 spring 源碼對比看它的實現
3)Spring 源碼解讀java

1. 構造參數依賴注入

org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(String, RootBeanDefinition, Constructor<?>[], Object[])spring

->
org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(String, RootBeanDefinition, BeanWrapper, ConstructorArgumentValues, ConstructorArgumentValues)緩存

、、app

->
org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(Object, Object)源碼分析

 

 解析構造參數源代碼的使用示例:post

    <bean id="combatService" factory-bean="loveServiceFactory" 
        factory-method="getCombatServiceFromMemberFactoryMethod" >
        <constructor-arg type="int" value="120" />
        <constructor-arg ref="beanA"></constructor-arg>
        <constructor-arg><bean id="ssss" class="cccc"></bean></constructor-arg>
        <constructor-arg><bean class="cccc"></bean></constructor-arg>
        <constructor-arg><map></map></constructor-arg>
        <constructor-arg><list></list></constructor-arg>
    </bean>

拓展:初始化前初始化後處理學習

實現BeanPostProcessor而後在裏面的初始化前和初始化後的方法裏面打斷點看調用棧就能夠找到初始化前和初始化後在哪裏處理的了測試

 實現BeanPostProcessor:this

package com.study.leesmall.spring.ext;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("------MyBeanPostProcessor.postProcessBeforeInitialization for " + beanName);
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("------MyBeanPostProcessor.postProcessAfterInitialization for " + beanName);
        return bean;
    }
}

在初始化前方法MyBeanPostProcessor.postProcessBeforeInitialization和MyBeanPostProcessor.postProcessAfterInitialization裏面打斷點拿到調用棧:spa

初始化前調用棧:

 

具體的初始化前處理:

 初始化後調用棧:

 具體的初始化後處理:

 

二、屬性依賴注入

寫個測試類裏面含有get和set方法,而後在set方法裏面打個斷點拿到調用棧去分析

測試類:

import org.springframework.stereotype.Component;

@Component
public class Bean3 {
    private String value;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

在xml裏面的配置:

    <bean id="bean3" class="com.study.leesmall.spring.sample.di.Bean3" scope="prototype" >
        <property name="value" value="10"></property>
    </bean>

測試入口:

package com.study.leesmall.spring.sample.di;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

public class DiMain {

    public static void main(String[] args) {
        ApplicationContext context = new GenericXmlApplicationContext(
                "classpath:com/study/leesmall/spring/sample/di/application.xml");

        Bean3 b3 = context.getBean(Bean3.class);
    }
}

在set方法裏面打個斷點拿到調用棧:

 

屬性依賴值處理源碼分析:

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(String, RootBeanDefinition, BeanWrapper)

 

3. 屬性循環依賴怎麼處理

構成參數是不容許循環依賴的,屬性是容許循環依賴的

1)示例代碼

Bean1:

package com.study.leesmall.spring.sample.di;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

public class Bean1 {

    @Autowired
    private Bean2 b2;

    public void do1() {
        System.out.println("------------------do1");
    }

    public Bean2 getB2() {
        return b2;
    }

    public void setB2(Bean2 b2) {
        this.b2 = b2;
    }
}

Bean2:

package com.study.leesmall.spring.sample.di;

import org.springframework.stereotype.Component;


public class Bean2 {

    @Autowired
    private Bean1 b1;

    public void do2() {
        b1.do1();
    }

    public Bean1 getB1() {
        return b1;
    }

    public void setB1(Bean1 b1) {
        this.b1 = b1;
    }
}

/spring-source-study/src/main/java/com/study/leesmall/spring/sample/di/application.xml配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="bean1" class="com.study.leesmall.spring.sample.di.Bean1" >
        <property name="b2" ref="bean2"></property>
    </bean>
    
    <bean id="bean2" class="com.study.leesmall.spring.sample.di.Bean2" >
        <property name="b1" ref="bean1"></property>
    </bean>
    
    
</beans>
    
    
    

測試類DiMain:

package com.study.leesmall.spring.sample.di;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

public class DiMain {

    public static void main(String[] args) {
        ApplicationContext context = new GenericXmlApplicationContext(
                "classpath:com/study/leesmall/spring/sample/di/application.xml");


        Bean2 b2 = context.getBean(Bean2.class);
        b2.do2();
    }
}

2)請分別嘗試在xml配置中配置以下三種狀況,看是否能夠循環依賴成功

a)Bean1和Bean2都是單例bean。

  循環依賴成功
b)Bean1和Bean2一個是單例的,一個是原型的。

  循環依賴成功
c)Bean1和Bean2都是原型的

  循環依賴不成功

3)思考:爲何兩個是原型時不能循環依賴,而單例時能夠?

一個Bean的屬性依賴另外一個bean實例,注入的過程是否就是從BeanFactory中getBean(),再賦值給屬性?

  是,首先從Bean工廠裏面獲取依賴的bean,沒有就建立

那爲何單例能夠,原型不能夠?

依據上面的邏輯,那就單例時能夠getBean()得到依賴的bean實例,原型時不能,爲何?
再來回想一下單例bean和原型bean在BeanFactory中的區別:

  單例Bean是緩存在BeanFactory中的,而原型Bean是不緩存的。

緩存和不緩存對於循環依賴的處理有什麼不一樣呢?

思考一下建立Bean實例的過程:
  先Bean1,建立Bean1的實例——>而後對其屬性進行依賴注入處理——>依賴Bean2,從BeanFactorygetBean("bean2")——>建立Bean2的實例——>對Bean2實例的屬性進行依賴注入處理——>依賴Bean1,從BeanFactory中獲取Bean1的實例
  緩存的就能夠經過beanFactory的getBean()得到前一個Bean的實例。而若是不緩存的,則bean2實例依賴注入Bean1時,從BeanFactorygetBean()就會又建立一個Bean1的實例,如此會無限循環依賴建立下去。

再仔細想一下,對於單例bean的緩存有時機的要求嗎?
  有,必定要在進行屬性依賴注入處理前緩存(暴露)到BeanFactory中。

4)看源碼找到單例Bean暴露(緩存)到BeanFactory中的代碼,它應在建立Bean實例後,進行屬性依賴注入前

在AbstractAutowireCapableBeanFactory.doCreateBean()方法中

        // 爲循環引用依賴提早緩存單例 bean
        // Eagerly cache singletons to be able to resolve circular references
        // even when triggered by lifecycle interfaces like BeanFactoryAware.
        // 是否提前暴露單例 Bean 實例
        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences
                && isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {
            if (logger.isTraceEnabled()) {
                logger.trace(
                        "Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
            }
            addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }

5)看一遍屬性注入的代碼邏輯驗證getBean(),單例的獲取

代碼在AbstractAutowireCapableBeanFactory.populateBean()中。
Xml配置方式的處理邏輯在方法最後的applyPropertyValues(beanName,mbd,bw,pvs);方法中。
註解的方式則在之上的InstantiationAwareBeanPostProcessor執行中:

        if (hasInstAwareBpps) {
            if (pvs == null) {
                pvs = mbd.getPropertyValues();
            }
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof InstantiationAwareBeanPostProcessor) {
                    InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                    PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
                    if (pvsToUse == null) {
                        if (filteredPds == null) {
                            filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
                        }
                        pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
                        if (pvsToUse == null) {
                            return;
                        }
                    }
                    pvs = pvsToUse;
                }
            }
        }

2、三種 Bean 配置方式的註冊、實例化過程

1. Xml

 BeanF:

package com.study.leesmall.spring.sample.config;

import org.springframework.beans.factory.annotation.Autowired;

public class BeanF {

    @Autowired
    private BeanE be;

    public void do1() {
        System.out.println("----------" + this + " do1");
        this.be.doSomething();
    }
}

BeanE:

package com.study.leesmall.spring.sample.config;

public class BeanE {

    public void doSomething() {
        System.out.println("-----" + this + " doSomething ");
    }
}

xml配置:

<?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"
    xmlns:aop="http://www.springframework.org/schema/aop"
    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.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    
    <bean id="beanE" class="com.study.leesmall.spring.sample.config.BeanE" />
    
    <bean id="beanF" class="com.study.leesmall.spring.sample.config.BeanF" ></bean>
    
    <context:annotation-config/>
    
    <context:component-scan base-package="com.study.leesmall.spring.sample.config" ></context:component-scan>
    
</beans>
    
    
    

測試類:

XMLConfigMain

package com.study.leesmall.spring.sample.config;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

public class XMLConfigMain {

    public static void main(String[] args) {
        ApplicationContext context = new GenericXmlApplicationContext(
                "classpath:com/study/leesmall/spring/sample/config/application.xml");

        BeanF bf = context.getBean(BeanF.class);
        bf.do1();

    }

}

測試類運行結果:

----------com.study.leesmall.spring.sample.config.BeanF@19d37183 do1
-----com.study.leesmall.spring.sample.config.BeanE@1a0dcaa doSomething 

2. Annotation 註解

 BeanG

package com.study.leesmall.spring.sample.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ImportResource;
import org.springframework.stereotype.Component;

@Component
@ImportResource("classpath:com/study/leesmall/spring/sample/config/application.xml")
public class BeanG {

    @Autowired
    private BeanF beanf;

    public void dog() {
        System.out.println("----------------------------------------");
        this.beanf.do1();
    }
}

在xml裏面開啓註解掃描:

    <context:annotation-config/>
    
    <context:component-scan base-package="com.study.leesmall.spring.sample.config" ></context:component-scan>

測試類:

AnnotationMain

package com.study.leesmall.spring.sample.config;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class AnnotationMain {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext("com.study.leesmall.spring.sample.config");

        BeanG bg = context.getBean(BeanG.class);
        bg.dog();
    }
}

測試結果:

----------------------------------------
----------com.study.leesmall.spring.sample.config.BeanF@6f204a1a do1
-----com.study.leesmall.spring.sample.config.BeanE@2de56eb2 doSomething 

3. Java based

 BeanH:

package com.study.leesmall.spring.sample.config;

import org.springframework.beans.factory.annotation.Autowired;

public class BeanH {

    @Autowired
    private BeanE be;

    public void doH() {
        System.out.println("-----------" + this + " doH");
        be.doSomething();
    }
}

測試類:

JavaBasedMain

package com.study.leesmall.spring.sample.config;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.study.leesmall.spring.sample.config")
public class JavaBasedMain {

    @Bean
    public BeanH getBeanH() {
        return new BeanH();
    }

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(JavaBasedMain.class);

        BeanH bh = context.getBean(BeanH.class);
        bh.doH();
    }
}

測試結果:

-----------com.study.leesmall.spring.sample.config.BeanH@475e586c doH
-----com.study.leesmall.spring.sample.config.BeanE@657c8ad9 doSomething 

問題:

一、 xml 方式中怎麼開啓註解支持?

在application.xml裏面加入以下配置:

    <context:annotation-config/>
    
    <context:component-scan base-package="com.study.leesmall.spring.sample.config" ></context:component-scan>

二、xml方式中開啓註解支持,是如何實現的?該怎麼去看?你會怎麼實現?

入口在Spring的E:\repository\org\springframework\spring-context\5.1.3.RELEASE\spring-context-5.1.3.RELEASE.jar /META-INF/spring.handlers裏面

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler

ContextNamespaceHandler就是入口:

 

類org.springframework.context.annotation.AnnotationConfigBeanDefinitionParser就是用來解析下面的配置的:

<context:annotation-config/>

 

 

註冊註解配置處理器的方法org.springframework.context.annotation.AnnotationConfigUtils.registerAnnotationConfigProcessors(BeanDefinitionRegistry, Object)

 

Autowired註解的處理屬性注入的方法:

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(PropertyValues, Object, String)

 

org.springframework.beans.factory.annotation.InjectionMetadata.InjectedElement.inject(Object, String, PropertyValues):

三、Xml中的<context:component-scan>是如何實現的?四、註解方式能夠嵌入xml嗎?五、Javabase方式各註解的解析發生在哪

相關文章
相關標籤/搜索