手寫Spring---DI依賴注入(2)

接上一篇《手寫Spring---IOC容器(1)》繼續更新


1、DI分析

Q1:那些地方會有依賴?

1.構造參數依賴,上一篇中的幾種構建對象都不涉及傳入參數的問題
2.屬性依賴,構建出來的對象屬性會存在依賴
複製代碼

Q2:依賴注入的本質是什麼?

賦值,給入構造參數值,還有對屬性的賦值
複製代碼

Q3:參數值,屬性值,多是什麼值?

直接賦予的值和bean依賴(使用另外的bean)
複製代碼

Q4:直接賦予的值會有哪幾種類型?

1.基本數據類型,String
2.數組,集合
3.Properties
4.map
5.對象
複製代碼

結論:不管是參數值仍是屬性值,bean工廠在進行DI依賴注入時的本質就是進行賦值

Q1:如何告訴bean工廠該給入什麼構造參數值?即如何來定義參數依賴?

Q2:如何來定義屬性依賴?


2、DI實現

① DI依賴注入-構造參數依賴定義分析:

以一個Girl類爲例

public class Girl {
    public Girl(String name,int age,char cup,Boy boyfriend){}
}
複製代碼

Q1:咱們要建立一個Girl類是如何建立的?

Boy Mike = new Boy();
Girl beauty = new Girl("WT",24,'A',Mike);
這種時候直接賦值,很是簡單
複製代碼

Q2:這種時候的構造參數依賴是怎樣的?

1.第一個參數值"WT"
2.第二個參數值"24"
3.第三個參數值是'A'
4.第四個參數值是一個Boy的bean
複製代碼

② 此時咱們能夠進行一個DI的構造參數依賴設計了

Q1:參數能夠多個,使用什麼來進行存儲?

集合或者數組
複製代碼

Q2:參數有順序,如何處理順序?

按參數順序放入List便可
複製代碼

Q3:參數值能夠爲直接的賦值,也能夠爲bean依賴,如何去表示?

只能使用Object類型:List<Object> constructorArgumentValues
複製代碼

Q4:若是使用了Object來表示值,如何區分是否爲bean依賴?

此時咱們須要爲bean依賴定義一個數據類型BeanReference,
bean工廠在構造bean實例時須要進行遍歷參數是否爲
BeanReference類型,若是是,替換成依賴的bean實例
複製代碼

Q5:若是直接賦值中存在數組集合,它們中的某元素存在bean依賴,如何處理?

元素值仍是使用BeanReference,
bean工廠在使用時應該遍歷此數組/集合,存在即替換
複製代碼

Q6:這個BeanReference該是怎樣的?

/**
* 用於依賴注入中描述bean依賴
*/
public class BeanReference {

    private String beanName;
    
    public BeanReference(String beanName){
        super();
        this.beanName = beanName;
    }

    /**
     * 得到引用的beanName
     * @return
     */
    public String getBeanName(){
        return this.beanName;
    }
}
複製代碼

這個類僅僅是做爲說明bean依賴的,須要提供一個beanName參數和一個getBeanName方法便可


③ DI實現-構造參數依賴定義(可對照上一篇參考)

1.在BeanDefinition中增長獲取構造參數值的接口,讓用戶在定義bean時能指定構造參數值

BeanDefinition---List<?> getConstructorArgumentValues();  
複製代碼

2.在實現了BeanDefinition接口的GeneralBeanDefinition中添加實現,由於使用了lombok插件再也不須要手寫getter和setter,因此咱們添加表示構造參數值的字段便可

private List<?> constructorArgumentValues;
複製代碼

④ DI實現-BeanFactory中實現構造參數依賴注入1

(1)首先須要把bean定義中的構造參數引用轉化爲真實的值,在DefaultBeanFactory中增長一個方法getConstructorArgumentValues來完成這件事

private Object[] getConstructorArgumentValues(BeanDefinition beanDefinition) throws Exception{
    return this.getRealValues(beanDefinition.getConstructorArgumentValues());
}
複製代碼

此時咱們把這個獲取值的過程提取出來單獨爲一個方法

private Object[] getRealValues(List<?> defs) throws Exception{
    if (CollectionUtils.isEmpty(defs)){return null;}
    Object[] values = new Object[defs.size()];
    int i = 0;
    //values數組的元素
    Object value = null;
    for (Object realValue : defs){
        if (realValue == null){
            value = null;
        }else if (realValue instanceof BeanReference){
            value = this.doGetBean(((BeanReference) realValue).getBeanName());
            ···
        }else {value = realValue;}
        values[i++] = value;
    }
    return values;
}
複製代碼

tips:這裏的CollectionUtils使用的是apache的,else if那裏省略了一部分的方法,具體作法就是建立一個工具類,而後去處理不一樣數據類型的數據中的bean引用,分支有大概4個,數組Object[],集合Collection,properties,和Map

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-collections4</artifactId>
  <version>4.1</version>
</dependency>
複製代碼

⑤ DI實現-BeanFactory中實現構造參數依賴注入2

Q1:有參數了,如何判定是哪一個構造方法,哪一個工廠方法?

1.方法是能夠重載的(參數數量和參數類型的判斷)
2.形參定義時多是接口或者父類,實參是具體的子實現
3.反射提供的獲取的構造方法,方法的API(如下的Class指的都是**參數**的類型)
(getConstrutors()和getConstructor(Class<?>...))
(getMethods()和getMethod(String,Class<?>...))
複製代碼

判斷邏輯:

1.先根據參數的類型進行精確匹配查找,若是沒有找到,則進行第二步查找
2.獲取全部的構造方法遍歷,經過參數數量過濾,再比對形參類型和實參類型
(爲何第一步沒法找到,也就是上面說起到的 「形參定義時多是接口或者父類,實參是具體的子實現」 這個緣由)
複製代碼

Q2:當咱們判斷出構造方法或者工廠方法後,對於原型bean(prototype),下次獲取Bean是否能夠省略判斷過程(設置多例時每次獲取須要從新構造)

對於prototype,咱們能夠緩存這個構造方法或工廠方法
咱們在BeanDefinition中增長緩存的方法

//如下4個方法僅供BeanFactory使用
public Constructor<?> getConstructor();
public void setConstructor(Constructor<?> constructor);
public Method getFactoryMethod();
public void setFactoryMethod(Method method);
複製代碼

因爲在BeanDefinition接口中增長了上面4個方法,因此在GeneralBeanDefinition添加實現

private Constructor<?> constructor;
private Method factoryMethod;

@Override
public Constructor<?> getConstructor() {
    return this.constructor;
}

@Override
public void setConstructor(Constructor<?> constructor) {
    this.constructor = constructor;
}

@Override
public Method getFactoryMethod() {
    return this.factoryMethod;
}

@Override
public void setFactoryMethod(Method method) {
    this.factoryMethod = factoryMethod;
}
複製代碼

接下來就能夠去實現構造方法和工廠方法的代碼

⑥ DI實現-BeanFactory中實現構造參數依賴注入3

(1) 在DefaultBeanFactory中增長查找構造方法的方法

private Constructor determineConstructor(BeanDefinition beanDefinition,Object[] args) throws Exception{
    Constructor constructor = null;

    //當沒有任何一個參數時直接獲取無參構造方法
    if (args == null){
        return beanDefinition.getBeanClass().getConstructor(null);
    }

    //對於原型bean,第二次開始獲取Bean實例時,可直接獲取第一次緩存的構造方法
    constructor = beanDefinition.getConstructor();
    if (constructor != null){
        return constructor;
    }

    //根據參數類型獲取精確匹配的構造方法
    Class<?>[] paramTypes = new Class[args.length];
    int j = 0;
    for (Object paramType : args){
        paramTypes[j++] = paramType.getClass();
    }
    try {
        constructor = beanDefinition.getConstructor();
    }catch (Exception e){
        //此異常不須要進行處理
    }

    if (constructor == null){
        //把全部的構造器所有遍歷出來一一比對
        Outer: for (Constructor<?> allConstructor : beanDefinition.getBeanClass().getConstructors()){
            Class<?>[] pTypes = allConstructor.getParameterTypes();
            //此構造方法的參數長度等於提供參數長度
            if (pTypes.length == args.length){
                for (int i = 0;i<pTypes.length;i++){

                    //若是第一個參數的類型就已經不匹配了,就直接再也不繼續比對了,直接跳轉到外循環
                    if (!pTypes[i].isAssignableFrom(args[i].getClass())){
                        continue Outer;
                    }
                }

                //若是以上皆匹配的話,就直接獲取到這個構造器,而後直接讓循環終止
                constructor = allConstructor;
                break Outer;
            }
        }
    }

    if (constructor != null){
        if (beanDefinition.isPrototype()){
            //對原型bean構造器進行緩存方便下次查找
            beanDefinition.setConstructor(constructor);
        }
        return constructor;
    }else {
        throw new Exception("不存在對應的構造方法!"+beanDefinition);
    }
}
複製代碼

修改構造方法建立對象的實現

前一篇的構造方法
private Object createInstanceByConstructor(BeanDefinition beanDefinition) throws IllegalAccessException, InstantiationException {
    try{
        return beanDefinition.getBeanClass().newInstance();
    } catch (SecurityException e){
        logger.error("建立bean的實例異常,beanDefinition"+beanDefinition,e);
        throw e;
    }
}
複製代碼
修改後版本
private Object createInstanceByConstructor(BeanDefinition beanDefinition) throws Exception {
    try {
        //獲取真正的參數值
        Object[] args = this.getConstructorArgumentValues(beanDefinition);
        if (args == null) {
            return beanDefinition.getBeanClass().newInstance();
        } else {
            // 決定構造方法
            return this.determineConstructor(beanDefinition, args).newInstance(args);
        }
    } catch (SecurityException e1) {
        logger.error("建立bean的實例異常,beanDefinition:" + beanDefinition, e1);
        throw e1;
    }
}
複製代碼

天然工廠方法也要進行一樣的編寫查找方法和決定方法的邏輯實現

工廠查找方法
private Method determineFactoryMethod(BeanDefinition bd, Object[] args, Class<?> type) throws Exception {
    if (type == null) {
        type = bd.getBeanClass();
    }
    String methodName = bd.getFactoryMethodName();
    if (args == null) {
        return type.getMethod(methodName, null);
    }
    Method m = null;
    // 對於原型bean,從第二次開始獲取bean實例時,可直接得到第一次緩存的構造方法。
    m = bd.getFactoryMethod();
    if (m != null) {
        return m;
    }
    // 根據參數類型獲取精確匹配的方法
    Class[] paramTypes = new Class[args.length];
    int j = 0;
    for (Object p : args) {
        paramTypes[j++] = p.getClass();
    }
    try {
        m = type.getMethod(methodName, paramTypes);
    } catch (Exception e) {
        // 這個異常不須要處理
    }
    if (m == null) {
        // 沒有精確參數類型匹配的,則遍歷匹配全部的方法
        // 判斷邏輯:先判斷參數數量,再依次比對形參類型與實參類型
        outer: for (Method m0 : type.getMethods()) {
            if (!m0.getName().equals(methodName)) {
                continue;
            }
            Class<?>[] paramterTypes = m.getParameterTypes();
            if (paramterTypes.length == args.length) {
                for (int i = 0; i < paramterTypes.length; i++) {
                    if (!paramterTypes[i].isAssignableFrom(args[i].getClass())) {
                        continue outer;
                    }
                }
                m = m0;
                break outer;
            }
        }
    }
    if (m != null) {
        // 對於原型bean,能夠緩存找到的方法,方便下次構造實例對象。在BeanDefinfition中獲取設置所用方法的方法。
        if (bd.isPrototype()) {
            bd.setFactoryMethod(m);
        }
        return m;
    } else {
        throw new Exception("不存在對應的構造方法!" + bd);
    }
}
複製代碼
前一篇的靜態工廠方法:
private Object createInstanceByStaticFactoryMethod(BeanDefinition beanDefinition) throws Exception{
    Class<?> type = beanDefinition.getBeanClass();
    Method method = type.getMethod(beanDefinition.getFactoryMethodName(),null);
    return method.invoke(type,null);
}
複製代碼
修改後的靜態工廠方法:
private Object createInstanceByStaticFactoryMethod(BeanDefinition beanDefinition) throws Exception{
    Class<?> type = beanDefinition.getBeanClass();
    Object[] realArgs = this.getRealValues(beanDefinition.getConstructorArgumentValues());
    Method m = this.determineFactoryMethod(beanDefinition, realArgs, null);
    return m.invoke(type, realArgs);
}
複製代碼
前一篇的工廠bean方法:
private Object createInstanceByFactoryBean(BeanDefinition beanDefinition) throws Exception{
    Object factoryBean = this.doGetBean(beanDefinition.getFactoryBeanName());
    Method method = factoryBean.getClass().getMethod(beanDefinition.getFactoryMethodName(),null);
    return method.invoke(factoryBean,null);
}
複製代碼
修改後的工廠bean方法:
private Object createInstanceByFactoryBean(BeanDefinition beanDefinition) throws Exception{
    Object factoryBean = this.doGetBean(beanDefinition.getFactoryBeanName());
    Object[] realArgs = this.getRealValues(beanDefinition.getConstructorArgumentValues());
    Method m = this.determineFactoryMethod(beanDefinition, realArgs, factoryBean.getClass());
    return m.invoke(factoryBean, realArgs);
}
複製代碼

修改完構造,靜態工廠,工廠bean的方法後進行一下測試看可否正常工做了

⑦ DI實現-BeanFactory中實現構造參數依賴注入4

Q:循環依賴如何進行處理?

1.構造對象時能夠循環依賴嗎?
A:不能夠在構造實例對象時的循環依賴
2.如何發現循環依賴?
A:加入一個正在構造的bean的記錄,每一個bean開始構造時加入到記錄中,構造完成就移走,
若是有依賴,先看依賴的bean是否在構造中,若是是,就構成了循環,拋出異常
複製代碼

代碼實現:

DefaultBeanFactory.java---添加一個字段:
private ThreadLocal<Set<String>> buildingBeans = new ThreadLocal<>();
放進ThreadLocal的一個集合,是爲了線程安全
複製代碼

doGetBean方法內補充:

// 記錄正在建立的Bean
    Set<String> ingBeans = this.buildingBeans.get();
    if (ingBeans == null) {
        ingBeans = new HashSet<>();
        this.buildingBeans.set(ingBeans);
    }

    // 檢測循環依賴
    if (ingBeans.contains(beanName)) {
        throw new Exception(beanName + " 循環依賴!" + ingBeans);
    }

    // 記錄正在建立的Bean
    ingBeans.add(beanName);
複製代碼

還有建立完成後···

// 建立好實例後,移除建立中記錄
    ingBeans.remove(beanName);
複製代碼

DI實現-屬性依賴設計&實現1

Q1:屬性依賴是什麼?

某個屬性依賴某個值
複製代碼

Q2:該如何來描述一個屬性依賴?

屬性名,值,定義一個類,表示這倆個值
複製代碼

Q3:會有多個依賴該如何存放?

List
複製代碼

Q4:屬性值的狀況和構造參數值同樣嗎?

同樣
複製代碼

定義屬性依賴描述實體類PropertyValue

import lombok.Data;

@Data
public class PropertyValue {
    private String name;

    private Object value;

}
複製代碼

DI實現-屬性依賴設計&實現2

在BeanDefinition中添加屬性依賴定義的接口

List<PropertyValue> getPropertyValues();
複製代碼

在GeneralBeanDefinition中添加實現

private List<PropertyValue> propertyValues;
public List<PropertyValue> getPropertyValues() {
    return propertyValues;
}

public void setPropertyValues(List<PropertyValue> propertyValues) {
    this.propertyValues = propertyValues;
}
複製代碼

在DefaultBeanFactory的doGetBean方法中增長對屬性依賴的調用

// 建立好實例後,移除建立中記錄
    ingBeans.remove(beanName);

    // 給入屬性依賴
    this.setPropertyDIValues(bd, instance);

    // 執行初始化方法
    this.doInit(bd, instance);
複製代碼

setPropertyDIValues方法的實現(和getRealValues方法相似)

private void setPropertyDIValues(BeanDefinition bd, Object instance) throws Exception {
    if (CollectionUtils.isEmpty(bd.getPropertyValues())) {
        return;
    }
    for (PropertyValue pv : bd.getPropertyValues()) {
        if (StringUtils.isBlank(pv.getName())) {
            continue;
        }
        Class<?> clazz = instance.getClass();
        Field p = clazz.getDeclaredField(pv.getName());

        p.setAccessible(true);

        Object rv = pv.getValue();
        Object v = null;
        if (rv == null) {
            v = null;
        } else if (rv instanceof BeanReference) {
            v = this.doGetBean(((BeanReference) rv).getBeanName());
        } else if (rv instanceof Object[]) {
            // TODO 處理集合中的bean引用
        } else if (rv instanceof Collection) {
            // TODO 處理集合中的bean引用
        } else if (rv instanceof Properties) {
            // TODO 處理properties中的bean引用
        } else if (rv instanceof Map) {
            // TODO 處理Map中的bean引用
        } else {
            v = rv;
        }

        p.set(instance, v);

    }
}
複製代碼

如下是測試篇(可不看)

A,ABeanFactory,C,CC,D~F的bean設計(反正就是一堆測試用的類把,分別知足測試另外bean的引用,父類子類,循環引用和屬性依賴)

ABean

package MySpring.DITestUtils;

public class ABean {

    private String name;

    private CBean cb;

    public ABean(String name, CBean cb) {
        super();
        this.name = name;
        this.cb = cb;
        System.out.println("調用了含有CBean參數的構造方法");
    }

    public ABean(String name, CCBean cb) {
        super();
        this.name = name;
        this.cb = cb;
        System.out.println("調用了含有CCBean參數的構造方法");
    }

    public ABean(CBean cb) {
        super();
        this.cb = cb;
    }

    public void doSomthing() {
        System.out.println(System.currentTimeMillis() + " " + this.name + " cb.name=" + this.cb.getName());
    }

    public void init() {
        System.out.println("ABean.init() 執行了");
    }

    public void destroy() {
        System.out.println("ABean.destroy() 執行了");
    }
}
複製代碼

ABeanFactory

public class ABeanFactory {

    public static ABean getABean(String name, CBean cb) {
        return new ABean(name, cb);
    }

    public ABean getABean2(String name, CBean cb) {
        return new ABean(name, cb);
    }
}
複製代碼

CBean

public class CBean {

    private String name;

    public CBean(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
複製代碼

}java

CCBean

public class CCBean extends CBean {

    public CCBean(String name) {
        super(name);
    }
複製代碼

}apache

DBean

public class DBean {

    private EBean ebean;

    public DBean(EBean ebean) {
        super();
        this.ebean = ebean;
    }
複製代碼

}數組

EBean

public class EBean {

    private DBean dbean;

    public EBean(DBean dbean) {
        super();
        this.dbean = dbean;
    }
複製代碼

}緩存

FBean(爲了省事用了@Data)

@Data
public class FBean {

    private String name;

    private int age;

    private ABean aBean;
複製代碼

}安全

測試類1---DITest

public class DITest {
    static PreBuildBeanFactory bf = new PreBuildBeanFactory();

    @Test
    public void testConstructorDI() throws Exception {

        GeneralBeanDefinition bd = new GeneralBeanDefinition();
        bd.setBeanClass(ABean.class);
        List<Object> args = new ArrayList<>();
        args.add("abean");
        args.add(new BeanReference("cbean"));
        bd.setConstructorArgumentValues(args);
        bf.registerBeanDefinition("abean", bd);

        bd = new GeneralBeanDefinition();
        bd.setBeanClass(CBean.class);
        args = new ArrayList<>();
        args.add("cbean");
        bd.setConstructorArgumentValues(args);
        bf.registerBeanDefinition("cbean", bd);

        bf.preInstantiateSingletons();

        ABean abean = (ABean) bf.getBean("abean");

        abean.doSomthing();
    }

    @Test
    public void testStaticFactoryMethodDI() throws Exception {

        GeneralBeanDefinition bd = new GeneralBeanDefinition();
        bd.setBeanClass(ABeanFactory.class);
        bd.setFactoryMethodName("getABean");
        List<Object> args = new ArrayList<>();
        args.add("abean02");
        args.add(new BeanReference("cbean02"));
        bd.setConstructorArgumentValues(args);
        bf.registerBeanDefinition("abean02", bd);

        bd = new GeneralBeanDefinition();
        bd.setBeanClass(CBean.class);
        args = new ArrayList<>();
        args.add("cbean02");
        bd.setConstructorArgumentValues(args);
        bf.registerBeanDefinition("cbean02", bd);

        bf.preInstantiateSingletons();

        ABean abean = (ABean) bf.getBean("abean02");

        abean.doSomthing();
    }

    @Test
    public void testFactoryMethodDI() throws Exception {

        GeneralBeanDefinition bd = new GeneralBeanDefinition();
        bd.setFactoryBeanName("abeanFactory");
        bd.setFactoryMethodName("getABean2");
        List<Object> args = new ArrayList<>();
        args.add("abean03");
        args.add(new BeanReference("cbean02"));
        bd.setConstructorArgumentValues(args);
        bf.registerBeanDefinition("abean03", bd);

        bd = new GeneralBeanDefinition();
        bd.setBeanClass(ABeanFactory.class);
        bf.registerBeanDefinition("abeanFactory", bd);

        bf.preInstantiateSingletons();

        ABean abean = (ABean) bf.getBean("abean03");

        abean.doSomthing();
    }

    @Test
    public void testChildTypeDI() throws Exception {

        GeneralBeanDefinition bd = new GeneralBeanDefinition();
        bd.setBeanClass(ABean.class);
        List<Object> args = new ArrayList<>();
        args.add("abean04");
        args.add(new BeanReference("ccbean01"));
        bd.setConstructorArgumentValues(args);
        bf.registerBeanDefinition("abean04", bd);

        bd = new GeneralBeanDefinition();
        bd.setBeanClass(CCBean.class);
        args = new ArrayList<>();
        args.add("Ccbean01");
        bd.setConstructorArgumentValues(args);
        bf.registerBeanDefinition("ccbean01", bd);

        bf.preInstantiateSingletons();

        ABean abean = (ABean) bf.getBean("abean04");

        abean.doSomthing();
    }
}
複製代碼

DITest的測試結果

調用了含有CCBean參數的構造方法
1555720164146 abean04 cb.name=Ccbean01
調用了含有CBean參數的構造方法
1555720164162 abean cb.name=cbean
調用了含有CBean參數的構造方法
1555720164162 abean02 cb.name=cbean02
調用了含有CBean參數的構造方法
1555720164162 abean03 cb.name=cbean02
複製代碼

CirculationDITest的測試結果

java.lang.Exception: dbean 循環依賴![ebean, dbean]
複製代碼

PropertyDITest的測試結果

調用了含有CBean參數的構造方法
FFBean01 18
1555720275503 abean01 cb.name=cbean01
複製代碼



Tips:在GeneralBeanDefinition使用lombok插件時候曾經有報錯,就是在測試類中設置的參數一直沒法傳入到DefaultBeanFactory的createInstanceByConstructor方法中(此時第一個if (args == null)會一直判斷爲正確),緣由我也還沒弄清楚,實在很差意思···

相關文章
相關標籤/搜索