前言

本文是「如何实现一个简易版的 Spring」系列的第二篇,在 第一篇 介绍了如何实现一个基于 XML 的简略 Setter 注入,这篇来看看要如何去实现一个简略的 Constructor 注入性能,实现步骤和 Setter 注入是一样的“套路”,先设计一个数据结构去解析表白 XML 配置文件里的信息,而后再应用这些解析好的数据结构做一些事件,比方这里的 Constructor 注入。话不多说,上面咱们间接进入正题。

数据结构设计

应用 Constructor 注入形式的 XML 的一种配置如下所示:

<bean id="orderService" class="cn.mghio.service.version3.OrderService">    <constructor-arg ref="stockService"/>    <constructor-arg ref="tradeService"/>    <constructor-arg type="java.lang.String" value="mghio"/></bean>

以上 OrderService 类如下:

/** * @author mghio * @since 2021-01-16 */public class OrderService {    private StockDao stockDao;    private TradeDao tradeDao;    private String owner;    public OrderService(StockDao stockDao, TradeDao tradeDao, String owner) {        this.stockDao = stockDao;        this.tradeDao = tradeDao;        this.owner = owner;    }}

XML 的配置构造上看和 Setter 注入相似,都是 Key-Value 类的格局,能够将每个 constructor-arg 节点形象为 ValueHolder,蕴含理论解析后的值类型 value、类型 type 以及参数名称 name,如下所示:

/** * @author mghio * @since 2021-01-16 */public class ValueHolder {    private Object value;    private String type;    private String name;    // omit setter and getter }    

同样一个 Bean 能够蕴含多个 ValueHolder,为了封装实现以及不便提供一些判断办法(比方是否配置有结构器注入等),将进一步封装为 ConstructorArgument,并提供一些 CRUD 接口,而 ValueHolder 作为外部类,如下所示:

/** * @author mghio * @since 2021-01-16 */public class ConstructorArgument {    private final List<ValueHolder> argumentsValues = new LinkedList<>();    public void addArgumentValue(Object value) {        this.argumentsValues.add(new ValueHolder(value));    }    public List<ValueHolder> getArgumentsValues() {        return this.argumentsValues;    }    public int getArgumentCount() {        return this.argumentsValues.size();    }    public boolean isEmpty() {        return this.argumentsValues.isEmpty();    }    public void clear() {        this.argumentsValues.clear();    }    // some other methods...    public static class ValueHolder {        private Object value;        private String type;        private String name;    }}

而后在 BeanDefinition 接口中减少获取 ConstructorArgument 办法和判断是否配置 ConstructorArgument 办法。构造如下图所示:

解析 XML 配置文件

有了 上篇文章) 的根底,解析 XML 也比较简单,这里咱们解析的是 constructor-arg 节点,组装数据增加到 BeanDefinitionConstructorArgument 属性中,批改 XmlBeanDefinitionReader 类的 loadBeanDefinition(Resource resource) 办法如下:

/** * @author mghio * @since 2021-01-16 */public class XmlBeanDefinitionReader {    private static final String CONSTRUCTOR_ARG_ELEMENT = "constructor-arg";    private static final String NAME_ATTRIBUTE = "name";    private static final String TYPE_ATTRIBUTE = "type";    // other fields and methods ...    public void loadBeanDefinition(Resource resource) {        try (InputStream is = resource.getInputStream()) {            SAXReader saxReader = new SAXReader();            Document document = saxReader.read(is);            Element root = document.getRootElement();  // <beans>            Iterator<Element> iterator = root.elementIterator();            while (iterator.hasNext()) {                Element element = iterator.next();                String beanId = element.attributeValue(BEAN_ID_ATTRIBUTE);                String beanClassName = element.attributeValue(BEAN_CLASS_ATTRIBUTE);                BeanDefinition bd = new GenericBeanDefinition(beanId, beanClassName);                if (null != element.attributeValue(BEAN_SCOPE_ATTRIBUTE)) {                    bd.setScope(element.attributeValue(BEAN_SCOPE_ATTRIBUTE));                }                // parse <constructor-arg> node                parseConstructorArgElements(element, bd);                parsePropertyElementValues(element, bd);                this.registry.registerBeanDefinition(beanId, bd);            }        } catch (DocumentException | IOException e) {            throw new BeanDefinitionException("IOException parsing XML document:" + resource, e);        }    }    private void parseConstructorArgElements(Element rootEle, BeanDefinition bd) {        Iterator<Element> iterator = rootEle.elementIterator(CONSTRUCTOR_ARG_ELEMENT);        while (iterator.hasNext()) {            Element element = iterator.next();            parseConstructorArgElement(element, bd);        }    }    private void parseConstructorArgElement(Element element, BeanDefinition bd) {        String typeAttr = element.attributeValue(TYPE_ATTRIBUTE);        String nameAttr = element.attributeValue(NAME_ATTRIBUTE);        Object value = parsePropertyElementValue(element, null);        ConstructorArgument.ValueHolder valueHolder = new ConstructorArgument.ValueHolder(value);        if (StringUtils.hasLength(typeAttr)) {            valueHolder.setType(typeAttr);        }        if (StringUtils.hasLength(nameAttr)) {            valueHolder.setName(nameAttr);        }        bd.getConstructorArgument().addArgumentValue(valueHolder);    }    // other fields and methods ...}

解析 XML 的过程整体上分为两步,第一步在遍历每个 <bean> 节点时判断 <constructor-arg> 节点是否存在,存在则解析 <constructor-arg> 节点;第二步将解析拼装好的 ValueHolder 增加到 BeanDefinition 中,这样咱们就把 XML 配置的 Constructor 注入解析到 BeanDefinition 中了,上面看看如何在创立 Bean 的过程中如何应用该数据结构进行结构器注入。

如何抉择 Constructor

很显著,应用结构器注入须要放在实例化 Bean的阶段,通过判断以后待实例化的 Bean 是否有配置结构器注入,有则应用结构器实例化。判断 XML 是否有配置结构器注入能够间接应用 BeanDefinition 提供的 hasConstructorArguments() 办法即可,实际上最终是通过判断 ConstructorArgument.ValueHolder 汇合是否有值来判断的。这里还有个问题 当存在多个结构器时如何抉择,比方 OrderService 类有如下三个构造函数:

/** * @author mghio * @since 2021-01-16 */public class OrderService {    private StockDao stockDao;    private TradeDao tradeDao;    private String owner;    public OrderService(StockDao stockDao, TradeDao tradeDao) {        this.stockDao = stockDao;        this.tradeDao = tradeDao;        this.owner = "nobody";    }    public OrderService(StockDao stockDao, String owner) {        this.stockDao = stockDao;        this.owner = owner;    }    public OrderService(StockDao stockDao, TradeDao tradeDao, String owner) {        this.stockDao = stockDao;        this.tradeDao = tradeDao;        this.owner = owner;    }}

XML 结构器注入的配置如下:

<bean id="orderService" class="cn.mghio.service.version3.OrderService">    <constructor-arg ref="stockService"/>    <constructor-arg ref="tradeService"/>    <constructor-arg type="java.lang.String" value="mghio"/></bean>

这时该如何抉择最适宜的结构器进行注入呢?这里应用的匹配办法是 1. 先判断结构函数参数个数,如果不匹配间接跳过,进行下一次循环;2. 当结构器参数个数匹配时再判断参数类型,如果和以后参数类型统一或者是以后参数类型的父类型则应用该结构器进行实例化。这个应用的判断办法比较简单间接,实际上 Spring 的判断形式思考到的状况比拟全面同时代码实现也更加简单,感兴趣的敌人能够查看 org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(...) 办法。这里须要留神的是,在解析 XML 配置的结构器注入参数时要进行类型转换为指标类型,将该类命名为 ConstructorResolver,实现代码比拟多这里就不贴出来了,能够到 GitHub 查看残缺代码。而后只须要在实例化 Bean 的时候判断是否存在结构器注入配置,存在则应用结构器注入即可,批改 DefaultBeanFactory 的实例化办法如下:

/** * @author mghio * @since 2021-01-16 */public class DefaultBeanFactory extends DefaultSingletonBeanRegistry implements ConfigurableBeanFactory,        BeanDefinitionRegistry {    // other fields and methods ...            private Object doCreateBean(BeanDefinition bd) {        // 1. instantiate bean        Object bean = instantiateBean(bd);        // 2. populate bean        populateBean(bd, bean);        return bean;    }    private Object instantiateBean(BeanDefinition bd) {        // 判断以后 Bean 的 `XML` 配置是否配置为结构器注入形式        if (bd.hasConstructorArguments()) {            ConstructorResolver constructorResolver = new ConstructorResolver(this);            return constructorResolver.autowireConstructor(bd);        } else {            ClassLoader classLoader = this.getClassLoader();            String beanClassName = bd.getBeanClassName();            try {                Class<?> beanClass = null;                Class<?> cacheBeanClass = bd.getBeanClass();                if (cacheBeanClass == null) {                    beanClass = classLoader.loadClass(beanClassName);                    bd.setBeanClass(beanClass);                } else {                    beanClass = cacheBeanClass;                }                return beanClass.getDeclaredConstructor().newInstance();            } catch (Exception e) {                throw new BeanCreationException("Created bean for " + beanClassName + " fail.", e);            }        }    }    // other fields and methods ...}

到这里就曾经实现了一个简易版的基于 XML 配置的 Constructor 注入了。

总结

本文简要介绍了 Spring 基于 XML 配置的 Constructor 注入,其实有了第一篇的 Setter 注入的根底,实现 Constructor 注入相对来说难度要小很多,这里的实现相对来说比较简单,然而其思维和大体流程是相似的,想要深刻理解 Spring 实现的具体细节能够查看源码。残缺代码已上传至 GitHub,感兴趣的敌人能够到这里 mghio-spring 查看残缺代码,下篇预报:「如何实现一个简易版的 Spring - 实现字段注解形式注入」