解析 XML 的过程整体上分为两步,第一步在遍历每个 节点时判断 节点是否存在,存在则解析 节点;第二步将解析拼装好的 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> <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 - 实现字段注解方式注入」。