前言

本文是「如何实现一个简易版的 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 节点,组装数据添加到 BeanDefinition 的 ConstructorArgument 属性中,修改 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 的过程整体上分为两步,第一步在遍历每个 节点时判断 节点是否存在,存在则解析 节点;第二步将解析拼装好的 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 - 实现字段注解方式注入」

如何实现一个简易版的 Spring - 如何实现 Constructor 注入的更多相关文章

  1. 如何实现一个简易版的 Spring - 如何实现 Setter 注入

    前言 之前在 上篇 提到过会实现一个简易版的 IoC 和 AOP,今天它终于来了...相信对于使用 Java 开发语言的朋友们都使用过或者听说过 Spring 这个开发框架,绝大部分的企业级开发中都离 ...

  2. 如何实现一个简易版的 Spring - 如何实现 @Component 注解

    前言 前面两篇文章(如何实现一个简易版的 Spring - 如何实现 Setter 注入.如何实现一个简易版的 Spring - 如何实现 Constructor 注入)介绍的都是基于 XML 配置文 ...

  3. 如何实现一个简易版的 Spring - 如何实现 @Autowired 注解

    前言 本文是 如何实现一个简易版的 Spring 系列第四篇,在 上篇 介绍了 @Component 注解的实现,这篇再来看看在使用 Spring 框架开发中常用的 @Autowired 注入要如何实 ...

  4. 如何实现一个简易版的 Spring - 如何实现 AOP(上)

    前言 本文是「如何实现一个简易版的 Spring 系列」的第五篇,在之前介绍了 Spring 中的核心技术之一 IoC,从这篇开始我们再来看看 Spring 的另一个重要的技术--AOP.用过 Spr ...

  5. 如何实现一个简易版的 Spring - 如何实现 AOP(中)

    前言 在上篇 如何实现 AOP(上) 介绍了 AOP 技术出现的原因和一些重要的概念,在我们自己实现之前有必要先了解一下 AOP 底层到底是如何运作的,所以这篇再来看看 AOP 实现所依赖的一些核心基 ...

  6. 如何实现一个简易版的 Spring - 如何实现 AOP(下)

    前言 前面两篇 如何实现 AOP(上).如何实现 AOP(中) 做了一些 AOP 的核心基础知识简要介绍,本文进入到了实战环节了,去实现一个基于 XML 配置的简易版 AOP,虽然是简易版的但是麻雀虽 ...

  7. 如何实现一个简易版的 Spring - 如何实现 AOP(终结篇)

    前言 在 上篇 实现了 判断一个类的方式是符合配置的 pointcut 表达式.根据一个 Bean 的名称和方法名,获取 Method 对象.实现了 BeforeAdvice.AfterReturni ...

  8. 依赖注入[5]: 创建一个简易版的DI框架[下篇]

    为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架.在<依赖注入[4]: 创建一个简易版的DI框架[上篇]> ...

  9. 依赖注入[4]: 创建一个简易版的DI框架[上篇]

    本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章(<控制反转>.<基于IoC的设计模式>和< 依赖注入模式>)从纯理论的角度 ...

随机推荐

  1. nginx配置访问本地资源

    参考博客:https://www.cnblogs.com/xy51/p/9973326.html 需要访问路径:http://IP:10013/p1upgrade/picfiles/image73b4 ...

  2. 博流BL602&BL604开发板介绍

    在2020松山湖论坛上,博流智能科技(南京)有限公司销售副总裁刘占领介绍了基于RISC-V核的低功耗.高可靠Wi-Fi+BLE二合一SoC芯片BL602.主要应用领域包括人工智能与工业互联网,特别是电 ...

  3. 移动端H5开发中的常见问题处理

    1.问题之合成海报: 功能技术:http://html2canvas.hertzen.com 问题描述:合成模糊.合成区域内容错位,合成不完整,合成边缘白条等. 解决方案:如有页面布局正常合成错位的, ...

  4. Python条件判断和循环语句

    一.条件判断语句 通过一条或多条语句的判断来决定是否执行代码块 1.if语句基本形式: if 判断条件:    语句块 例如: score=75if score>=60:    print &q ...

  5. Python高级语法-贯彻回顾-元类(4.99.1)

    @ 目录 1.为什么要掌握元类 2.正文 关于作者 1.为什么要掌握元类 在django中编写models的时候遇到了元类的相关操作 并且在mini-web框架编写的时候也遇到了相关的问题 意识到深入 ...

  6. EF生成模型时Disigner中无信息

    原博文 http://blog.sina.com.cn/s/blog_a1b63a730101ezs4.html 说明 DbContext是对ObjectContext的简化封装.原来的ObjectC ...

  7. (七)、touch--创建文件或者更新时间戳

    一.命令说明与格式 创建文件并更新时间戳,若要创建的文件名已经存在,则仅仅更新时间戳,而不改变其他任何信息 格式:touch     [选项]   目录名/文件名 选项: -a            ...

  8. MacOS下解决宿主机和docker容器之间网络互通

    docker在Mac下使用非常方便,官网提供了desktop版本的可视化软件,甚至还十分贴心地集成好了k8s套件.然而如果想同时部署和使用多个容器,每个容器不使用127.0.0.1地址,而是各自有ip ...

  9. 渗透工具Burp Suite浅析

    Burp suite是一款Web安全领域的跨平台工具,基于Java开发.它集成了很多用于发现常见Web漏洞的模块,如Proxy,Spider,Scanner,Intruder,Repeater等.所有 ...

  10. 持续提升程序员幸福指数——使用abp vnext设计一款面向微服务的单体架构

    可能你会面临这样一种情况,在架构设计之前,你对业务不甚了解,需求给到的也模棱两可,这个时候你既无法明确到底是要使用单体架构还是使用微服务架构,如果使用单体,后续业务扩展可能带来大量修改,如果使用微服务 ...