手写Spring---DI依赖注入(2)

1,689

接上一篇《手写Spring---IOC容器(1)》继续更新


一、DI分析

Q1:那些地方会有依赖?

1.构造参数依赖,上一篇中的几种构建对象都不涉及传入参数的问题
2.属性依赖,构建出来的对象属性会存在依赖

Q2:依赖注入的本质是什么?

赋值,给入构造参数值,还有对属性的赋值

Q3:参数值,属性值,可能是什么值?

直接赋予的值和bean依赖(使用另外的bean)

Q4:直接赋予的值会有哪几种类型?

1.基本数据类型,String
2.数组,集合
3.Properties
4.map
5.对象

结论:无论是参数值还是属性值,bean工厂在进行DI依赖注入时的本质就是进行赋值

Q1:如何告诉bean工厂该给入什么构造参数值?即如何来定义参数依赖?

Q2:如何来定义属性依赖?


二、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;
    }

}

CCBean

public class CCBean extends CBean {

    public CCBean(String name) {
        super(name);
    }

}

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)会一直判断为正确),原因我也还没弄清楚,实在不好意思···