(二)实现Bean属性依赖注入功能【手撸Spring】

一、前言

在上一篇手撸Spring之实现一个简易版IoC容器,我们先把最简单的IoC架子搭了起来,即实现了一个Bean的注入,体会了依赖反转的过程。这里提到的依赖反转,大家肯定非常耳熟了,也就是将业务对Bean的依赖反转了,往常我们在使用一个Bean的时候,会在我们的业务代码中通过new创建一个Bean实例,而在IoC框架里,new创建的动作交给了框架来实现并注入到业务代码中去交给业务使用,如下图所示:

接下来,我们在前一篇的基础上,进一步丰富框架的功能:当一个类中有多个属性时,在xml配置上bean字段内容,框架实现对字段的注入。

xml 复制代码
<?xml version="1.0" encoding="utf-8" ?>
<beans>
    <!--setter 注入-->
    <bean id="a1" class="com.tiny.spring.test.bean.A">
        <property name="property1" value="property1"/>
        <property name="property2" value="property2"/>
        <property name="property3" value="property3"/>
        <property type="Integer" name="age" value="24"/>
    </bean>

    <!--构造器 注入-->
    <bean id="a2" class="com.tiny.spring.test.bean.A">
        <constructor-arg name="property1" value="property1"/>
        <constructor-arg name="property2" value="property2"/>
        <constructor-arg name="property3" value="property3"/>
        <property type="Integer" name="age" value="24"/>
    </bean>
</beans>

二、属性注入的实现原理

在Spring框架,实现属性注入的方式由以下两种:

  • 构造器注入
  • setter方法注入

下面我们介绍下两种注入方式的原理。

1、构造器注入

简单来讲,实现构造器注入的过程就是框架对xml中<constructor-arg>标签解析、将该标签下的元数据和ConstructorArgumentValues对象建立关系,将其设置到BeanDefinition中,并在createBean时,通过反射的方式调用对应的构造器函数创建实例。

2、setter方法注入

和构造器注入原理类似,框架解析<property>标签数据,将其和PropertyValues建立映射关系并设置到BeanDefinition中,在createBean时,通过反射的方式调用对应的setXxx方法实现参数注入。

3、理论正确性验证

我们进入到源码中也可以看到,事实确实是如此:(ps: spring框架实现的要远复杂很多,我们只关注核心的流程)

3、Bean之间的依赖

我们前面提的属性都是普通属性,也就是基本类型+String,当涉及到其他引用类型需要注入时,Spring又是如何处理的?实际上,Spring设计的很巧妙,它在标签中增加了ref属性,这个属性就记了需要引用的另一个Bean,如下xml文件所示:

xml 复制代码
<?xml version="1.0" encoding="utf-8" ?>
<beans>
    <bean id="aService" class="com.tiny.spring.test.service.impl.AServiceImpl">
        <property type="com.tiny.spring.test.bean.A" name="a" ref="a1"/>
    </bean>
  
    <bean id="a1" class="com.tiny.spring.test.bean.A">
        <property name="property1" value="property1"/>
        <property name="property2" value="property2"/>
        <property name="property3" value="property3"/>
        <property type="Integer" name="age" value="24"/>
    </bean>
</beans>

Spring在加载xml文件时,会对ref属性做特殊处理,会对ref指定的bean做一次getBean(xx)操作,进而获取对应的Bean实例并注入到当前实例参数中。

循环依赖

如上所述,我们在处理Bean之间依赖的问题时,做这样的处理:

  • 获取实例A,发现容器中没有A的实例,则进入到实例A创建阶段
  • 创建A实例后,进行A实例属性填充时,发现其依赖B,则进入到获取B实例的阶段
  • 获取实例B,发现容器中没有B的实例,则进入到实例B创建阶段
  • 创建B实例后,进行B实例属性填充时,发现其又依赖A,则进入到获取A实例的阶段
  • ......

这样一个操作,我们发现循环了,这可蛋疼了!这个问题也是我们经常听到的循环依赖。那么Spring是如何解决的呢?

这就引入了三级缓存的概念:

  • 一级缓存:存储最终的对象实例
  • 二级缓存:存储早期对象引用,就是创建完实例后填充实例前
  • 三级缓存:存储对象工厂(这个我们后面涉及到AOP时,再做说明)

其实在针对一般场景(即两个普通Bean对象间的循环依赖),二级缓存就够了。我们本节实现的代码也仅是实现了二级缓存,第三级缓存我们在AOP环节再做介绍和补充。也不能说AOP环节,在BeanPostProcessor实现时就会介入,而AOP功能的实现也依赖于BeanPostProcessor。

三、代码实现

1、流程图

针对本节要实现的功能,给出下面简图,让大家有个简单的了解:

2、相关Bean

BeanDefinition

java 复制代码
package com.tiny.spring.beans.factory.config;

import com.tiny.spring.beans.factory.ArgumentValues;
import com.tiny.spring.beans.factory.PropertyValues;

/**
 * @author: markus
 * @date: 2023/10/7 8:14 PM
 * @Description: Bean配置元信息
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class BeanDefinition {
    String SCOPE_SINGLETON = "singleton";
    String SCOPE_PROTOTYPE = "prototype";
    /**
     * 是否是懒加载
     */
    private boolean lazyInit = false;
    /**
     *
     */
    private String[] dependsOn;
    private ArgumentValues constructorArgumentValues;
    private PropertyValues propertyValues;
    private String initMethodName;
    private volatile Object beanClass;
    private String id;
    private String className;
    private String scope = SCOPE_SINGLETON;

    // 构造器、setter、getter方法省略
}

PropertyValue

java 复制代码
package com.tiny.spring.beans.factory;

/**
 * @author: markus
 * @date: 2023/10/8 11:04 PM
 * @Description:
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class PropertyValue {
    private final String type;
    private final String name;
    private final Object value;
    private final boolean isRef;
  
    // 构造器、setter、getter方法省略
}

PropertyValues

java 复制代码
package com.tiny.spring.beans.factory;

import java.util.ArrayList;
import java.util.List;

/**
 * @author: markus
 * @date: 2023/10/8 11:17 PM
 * @Description: Bean属性对象集合抽象
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class PropertyValues {
    private final List<PropertyValue> propertyValueList;

    public PropertyValues() {
        this.propertyValueList = new ArrayList<>(0);
    }

    public List<PropertyValue> getPropertyValueList() {
        return this.propertyValueList;
    }

    public int size() {
        return this.propertyValueList.size();
    }

    public void addPropertyValue(PropertyValue pv) {
        this.propertyValueList.add(pv);
    }

//    public void addPropertyValue(String propertyName, Object propertyValue) {
//        addPropertyValue(new PropertyValue(propertyName, propertyValue));
//    }

    public void removePropertyValue(PropertyValue pv) {
        this.propertyValueList.remove(pv);
    }

    public void removePropertyValue(String propertyName) {
        this.propertyValueList.remove(getPropertyValue(propertyName));
    }

    public PropertyValue[] getPropertyValues() {
        return this.propertyValueList.toArray(new PropertyValue[0]);
    }

    public PropertyValue getPropertyValue(String propertyName) {
        for (PropertyValue pv : this.propertyValueList) {
            if (pv.getName().equals(propertyName))
                return pv;
        }
        return null;
    }

    public Object get(String propertyName) {
        PropertyValue pv = getPropertyValue(propertyName);
        return pv != null ? pv.getValue() : null;
    }

    public boolean contains(String propertyName) {
        return getPropertyValue(propertyName) != null;
    }

    public boolean isEmpty() {
        return this.propertyValueList.isEmpty();
    }
}

ArgumentValue

java 复制代码
package com.tiny.spring.beans.factory;

/**
 * @author: markus
 * @date: 2023/10/8 11:02 PM
 * @Description: 属性值抽象
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class ArgumentValue {
    private Object value;
    private String type;
    private String name;
}

ArgumentValues

java 复制代码
package com.tiny.spring.beans.factory;

import com.sun.istack.internal.Nullable;

import java.util.*;

/**
 * @author: markus
 * @date: 2023/10/8 11:06 PM
 * @Description: 构造器参数集合抽象
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class ArgumentValues {
    private final List<ArgumentValue> argumentValueList = new ArrayList<>();

    public ArgumentValues() {
    }

    public void addArgumentValue(ArgumentValue argumentValue) {
        this.argumentValueList.add(argumentValue);
    }

    public ArgumentValue getIndexedArgumentValue(int index) {
        ArgumentValue argumentValue = this.argumentValueList.get(index);
        return argumentValue;
    }

    public int getArgumentCount() {
        return (this.argumentValueList.size());
    }

    public boolean isEmpty() {
        return (this.argumentValueList.isEmpty());
    }
}

XmlBeanDefinitionReader

java 复制代码
package com.tiny.spring.beans.factory.xml;

import com.sun.media.sound.RIFFReader;
import com.tiny.spring.beans.factory.*;
import com.tiny.spring.beans.factory.config.BeanDefinition;
import com.tiny.spring.beans.factory.support.SimpleBeanFactory;
import com.tiny.spring.core.io.Resource;
import org.dom4j.Element;

import java.util.ArrayList;
import java.util.List;

/**
 * @author: markus
 * @date: 2023/10/7 8:50 PM
 * @Description:
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class XmlBeanDefinitionReader {
    SimpleBeanFactory beanFactory;

    public XmlBeanDefinitionReader(SimpleBeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    public void loadBeanDefinitions(Resource resource) {
        while (resource.hasNext()) {
            Element element = (Element) resource.next();
            String beanId = element.attributeValue("id");
            String className = element.attributeValue("class");
            BeanDefinition beanDefinition = new BeanDefinition(beanId, className);
            // 处理属性
            List<Element> propertyElements = element.elements("property");
            PropertyValues pvs = new PropertyValues();
            // 存储属性中的引用对象 bean的id
            List<String> refs = new ArrayList<>();
            for (Element propertyElement : propertyElements) {
                String pType = propertyElement.attributeValue("type");
                String pName = propertyElement.attributeValue("name");
                String pValue = propertyElement.attributeValue("value");
                String pRef = propertyElement.attributeValue("ref");
                String pV = "";
                boolean isRef = false;
                if (pValue != null && !pValue.equals("")) {
                    pV = pValue;
                } else if (pRef != null && !pRef.equals("")) {
                    isRef = true;
                    pV = pRef;
                    refs.add(pRef);
                }
                pvs.addPropertyValue(new PropertyValue(pType, pName, pV, isRef));
            }
            beanDefinition.setPropertyValues(pvs);
            String[] refArray = refs.toArray(new String[0]);
            beanDefinition.setDependsOn(refArray);

            // 处理构造器参数
            List<Element> constructorArgumentElements = element.elements("constructor-arg");
            ArgumentValues avs = new ArgumentValues();
            for (Element constructorArgumentElement : constructorArgumentElements) {
                String aType = constructorArgumentElement.attributeValue("type");
                String aName = constructorArgumentElement.attributeValue("name");
                String aValue = constructorArgumentElement.attributeValue("value");
                avs.addArgumentValue(new ArgumentValue(aValue, aType, aName));
            }
            beanDefinition.setConstructorArgumentValues(avs);
            this.beanFactory.registerBeanDefinition(beanId, beanDefinition);
        }
    }
}

BeanDefinitionRegistry

java 复制代码
package com.tiny.spring.beans.factory.support;

import com.tiny.spring.beans.factory.config.BeanDefinition;

/**
 * @author: markus
 * @date: 2023/10/7 11:57 PM
 * @Description: BeanDefinition注册器
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public interface BeanDefinitionRegistry {
    void registerBeanDefinition(String name, BeanDefinition bd);

    void removeBeanDefinition(String name);

    BeanDefinition getBeanDefinition(String name);

    boolean containsBeanDefinition(String name);
}

DefaultBeanDefinitionRegistry

java 复制代码
package com.tiny.spring.beans.factory.support;

import com.tiny.spring.beans.factory.config.SingletonBeanRegistry;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author: markus
 * @date: 2023/10/7 11:43 PM
 * @Description:
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {

    protected List<String> beanNames = new ArrayList<>();
    /*并发安全的单例Bean操作,保证容器中的Bean唯一*/
    protected Map<String, Object> singletons = new ConcurrentHashMap<>(256);
    /*早期Bean实例引用(实例被创建但还未被初始化),提前暴露,解决简单的循环依赖问题*/
    protected final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);

    @Override
    public void registerSingleton(String beanName, Object singletonObject) {
        synchronized (this.singletons) {
            this.singletons.put(beanName, singletonObject);
            this.beanNames.add(beanName);
        }
    }

    @Override
    public Object getSingleton(String beanName) {
        return this.singletons.get(beanName);
    }

    @Override
    public boolean containsSingleton(String beanName) {
        return this.singletons.containsKey(beanName);
    }

    @Override
    public String[] getSingletonNames() {
        return this.beanNames.toArray(new String[0]);
    }

    protected void removeSingleton(String beanName) {
        synchronized (this.singletons) {
            this.beanNames.remove(beanName);
            this.singletons.remove(beanName);
        }
    }
}

SingletonBeanRegistry

java 复制代码
package com.tiny.spring.beans.factory.config;

/**
 * @author: markus
 * @date: 2023/10/7 11:40 PM
 * @Description: 单例Bean注册器
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public interface SingletonBeanRegistry {
    /**
     * 单例Bean注册
     * @param beanName
     * @param singletonObject
     */
    void registerSingleton(String beanName, Object singletonObject);

    /**
     * 获取单例对象
     * @param beanName
     * @return
     */
    Object getSingleton(String beanName);

    /**
     * 是否包含某个单例Bean
     * @param beanName
     * @return
     */
    boolean containsSingleton(String beanName);

    /**
     * 获取单例BeanName集合
     * @return
     */
    String[] getSingletonNames();
}

SimpleBeanFactory

java 复制代码
package com.tiny.spring.beans.factory.support;

import com.tiny.spring.beans.BeansException;
import com.tiny.spring.beans.factory.*;
import com.tiny.spring.beans.factory.config.BeanDefinition;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author: markus
 * @date: 2023/10/7 8:52 PM
 * @Description:
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
public class SimpleBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory, BeanDefinitionRegistry {

    /* bean definition map*/
    private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
    private List<String> beanDefinitionNames = new ArrayList<>();

    public SimpleBeanFactory() {
    }

    @Override
    public Object getBean(String beanName) throws BeansException {
        // 先尝试直接拿bean实例
        Object singleton = getSingleton(beanName);
        if (singleton == null) {
            // 如果没有实例,则尝试从早期Bean引用缓存中去获取一下 todo 先在这里获取下,后续会像spring源码一样 抽象到DefaultSingletonBeanRegistry中去。
            singleton = this.earlySingletonObjects.get(beanName);
            if (singleton == null) {
                // 如果早期Bean引用缓存中还是没有,那就老老实实创建实例吧
                BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
                if (beanDefinition == null) {
                    throw new BeansException("The bean name does not exit. bean name [" + beanName + "]");
                } else {
                    // 获取Bean的定义
                    try {
                        singleton = createBean(beanDefinition);
                    } catch (BeansException e) {
                        throw e;
                    }
                    // 注册最终成形的Bean实例
                    this.registerSingleton(beanDefinition.getId(), singleton);
                }
            }
        }
        return singleton;
    }

    @Override
    public boolean containsBean(String beanName) {
        return containsSingleton(beanName);
    }

    @Override
    public boolean isSingleton(String beanName) {
        // todo 这里可能会抛空指针
        return getBeanDefinition(beanName).isSingleton();
    }

    @Override
    public boolean isPrototype(String beanName) {
        // todo 这里可能会抛空指针
        return getBeanDefinition(beanName).isPrototype();
    }

    @Override
    public Class<?> getType(String beanName) {
        // todo 这里可能会抛空指针
        return getBeanDefinition(beanName).getClass();
    }

    @Override
    public void registerBeanDefinition(String name, BeanDefinition bd) {
        this.beanDefinitionMap.put(name, bd);
        this.beanDefinitionNames.add(name);
        if (!bd.isLazyInit()) {
            try {
                getBean(name);
            } catch (BeansException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void removeBeanDefinition(String name) {
        this.beanDefinitionMap.remove(name);
        this.beanDefinitionNames.remove(name);
        this.removeSingleton(name);
    }

    @Override
    public BeanDefinition getBeanDefinition(String name) {
        return this.beanDefinitionMap.get(name);
    }

    @Override
    public boolean containsBeanDefinition(String name) {
        return this.beanDefinitionMap.containsKey(name);
    }

    private Object createBean(BeanDefinition beanDefinition) throws BeansException {
        Class<?> clazz = null;
        // 创建实例
        Object obj = doCreateBean(beanDefinition);
        // 将创建好的实例 存储到早期Bean引用缓存中
        this.earlySingletonObjects.put(beanDefinition.getId(), obj);
        try {
            clazz = Class.forName(beanDefinition.getClassName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        // 处理属性
        handleProperties(beanDefinition, clazz, obj);
        return obj;
    }

    /**
     * 仅仅是创建实例对象,并没有赋值
     *
     * @param bd
     * @return
     * @throws BeansException
     */
    private Object doCreateBean(BeanDefinition bd) throws BeansException {
        Class<?> clazz = null;
        Object obj = null;
        Constructor<?> constructor = null;

        try {
            clazz = Class.forName(bd.getClassName());
            // 处理构造器参数
            ArgumentValues argumentValues = bd.getConstructorArgumentValues();
            // 如果有参数
            if (!argumentValues.isEmpty()) {
                Class<?>[] paramTypes = new Class<?>[argumentValues.getArgumentCount()];
                Object[] paramValues = new Object[argumentValues.getArgumentCount()];
                // 对每一个参数,分数据类型分别处理
                for (int i = 0; i < argumentValues.getArgumentCount(); i++) {
                    ArgumentValue argumentValue = argumentValues.getIndexedArgumentValue(i);
                    if ("String".equals(argumentValue.getType()) || "java.lang.String".equals(argumentValue.getType())) {
                        paramTypes[i] = String.class;
                        paramValues[i] = argumentValue.getValue();
                    } else if ("Integer".equals(argumentValue.getType()) || "java.lang.Integer".equals(argumentValue.getType())) {
                        paramTypes[i] = Integer.class;
                        paramValues[i] = Integer.valueOf((String) argumentValue.getValue());
                    } else if ("int".equals(argumentValue.getType())) {
                        paramTypes[i] = int.class;
                        paramValues[i] = Integer.valueOf((String) argumentValue.getValue());
                    } else { //默认为string
                        paramTypes[i] = String.class;
                        paramValues[i] = argumentValue.getValue();
                    }
                }
                // 按照特定构造器创建实例
                constructor = clazz.getConstructor(paramTypes);
                obj = constructor.newInstance(paramValues);
            } else {
                // 如果没有构造器参数,则直接使用默认构造器创建实例即可
                obj = clazz.newInstance();
            }
        } catch (Exception e) {
            throw new BeansException("bean constructor invoke error,bean id is" + bd.getId());
        }
        return obj;
    }

    private void handleProperties(BeanDefinition bd, Class<?> clazz, Object obj) throws BeansException {
        // 处理属性
        System.out.println("handle properties for bean : " + bd.getId());
        // 如果有属性值的话,进行属性赋值
        PropertyValues pvs = bd.getPropertyValues();
        if (!pvs.isEmpty()) {
            for (int i = 0; i < pvs.getPropertyValues().length; i++) {
                // 对每一个属性,分数据类型分别处理
                PropertyValue propertyValue = pvs.getPropertyValues()[i];
                String pType = propertyValue.getType();
                String pName = propertyValue.getName();
                Object pValue = propertyValue.getValue();
                boolean isRef = propertyValue.isRef();

                Class[] paramTypes = new Class[1];
                Object[] paramValues = new Object[1];
                if (!isRef) {
                    // 如果不是 ref,只是普通属性,则对该属性分数据类型处理
                    if ("String".equals(pType) || "java.lang.String".equals(pType)) {
                        paramTypes[0] = String.class;
                        paramValues[0] = pValue;
                    } else if ("Integer".equals(pType) || "java.lang.Integer".equals(pType)) {
                        paramTypes[0] = Integer.class;
                        paramValues[0] = Integer.valueOf((String) pValue);
                    } else if ("int".equals(pType)) {
                        paramTypes[0] = int.class;
                        paramValues[0] = Integer.valueOf((String) pValue);
                    } else { // 默认为string
                        paramTypes[0] = String.class;
                        paramValues[0] = pValue;
                    }
                } else {
                    // 是 ref,则通过 getBean()方式获取引用Bean实例
                    try {
                        paramTypes[0] = Class.forName(pType);
                        paramValues[0] = getBean((String) pValue);
                    } catch (ClassNotFoundException e) {
                        throw new BeansException("ref bean " + pValue + " not found!");
                    }
                }


                // 按照setXxx规范查找setter方法,调用setter方法设置属性
                String methodName = "set" + pName.substring(0, 1).toUpperCase() + pName.substring(1);
                Method method = null;
                try {
                    method = clazz.getMethod(methodName, paramTypes);
                    method.invoke(obj, paramValues);
                } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                    throw new BeansException("bean setter method invoke error,bean id is " + bd.getId() + ",method is " + methodName);
                }

            }
        }
    }
}

四、本文总结

好了,本文介绍了构造器注入、setter方法注入的实现原理、Bean依赖的问题以及循环依赖问题Spring是如何做的,基于原理我们实现了一个非常简单的属性注入功能,目前类结构还不复杂,大家可以在我的github项目tiny-spring中直接查看,有不明白的地方也可以评论我。

相关推荐
泡芙冰淇淋ya9 分钟前
【spring boot项目】统一返回结果封装
java·spring boot·后端
qq_1887988711 分钟前
spring mvc学习
java·后端·学习·spring·mvc
互联网架构小马22 分钟前
12种增强Python代码的函数式编程技术
开发语言·后端·python·函数式编程
蝎子莱莱爱打怪36 分钟前
docker 重要且常用命令大全
java·spring cloud·docker·容器·eureka
.生产的驴1 小时前
Maven 分模块设计与开发 继承
java·maven
计算机周老师1 小时前
java-Linkedlist源码分析
java·开发语言·python
yava_free1 小时前
详解Java垃圾回收(GC)机制
java·jvm·算法
leeleezl1 小时前
【JVM】类加载机制
java·jvm·后端
掘根1 小时前
【Linux】touch
java·linux·服务器
fensioakq—qqq1 小时前
Spring框架的学习SpringMVC(1)
java·开发语言·后端·学习·spring