如何实现一个简易版的 Spring - 如何实现 Constructor 注入
前言
本文是「如何实现一个简易版的 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 注入的更多相关文章
- 如何实现一个简易版的 Spring - 如何实现 Setter 注入
前言 之前在 上篇 提到过会实现一个简易版的 IoC 和 AOP,今天它终于来了...相信对于使用 Java 开发语言的朋友们都使用过或者听说过 Spring 这个开发框架,绝大部分的企业级开发中都离 ...
- 如何实现一个简易版的 Spring - 如何实现 @Component 注解
前言 前面两篇文章(如何实现一个简易版的 Spring - 如何实现 Setter 注入.如何实现一个简易版的 Spring - 如何实现 Constructor 注入)介绍的都是基于 XML 配置文 ...
- 如何实现一个简易版的 Spring - 如何实现 @Autowired 注解
前言 本文是 如何实现一个简易版的 Spring 系列第四篇,在 上篇 介绍了 @Component 注解的实现,这篇再来看看在使用 Spring 框架开发中常用的 @Autowired 注入要如何实 ...
- 如何实现一个简易版的 Spring - 如何实现 AOP(上)
前言 本文是「如何实现一个简易版的 Spring 系列」的第五篇,在之前介绍了 Spring 中的核心技术之一 IoC,从这篇开始我们再来看看 Spring 的另一个重要的技术--AOP.用过 Spr ...
- 如何实现一个简易版的 Spring - 如何实现 AOP(中)
前言 在上篇 如何实现 AOP(上) 介绍了 AOP 技术出现的原因和一些重要的概念,在我们自己实现之前有必要先了解一下 AOP 底层到底是如何运作的,所以这篇再来看看 AOP 实现所依赖的一些核心基 ...
- 如何实现一个简易版的 Spring - 如何实现 AOP(下)
前言 前面两篇 如何实现 AOP(上).如何实现 AOP(中) 做了一些 AOP 的核心基础知识简要介绍,本文进入到了实战环节了,去实现一个基于 XML 配置的简易版 AOP,虽然是简易版的但是麻雀虽 ...
- 如何实现一个简易版的 Spring - 如何实现 AOP(终结篇)
前言 在 上篇 实现了 判断一个类的方式是符合配置的 pointcut 表达式.根据一个 Bean 的名称和方法名,获取 Method 对象.实现了 BeforeAdvice.AfterReturni ...
- 依赖注入[5]: 创建一个简易版的DI框架[下篇]
为了让读者朋友们能够对.NET Core DI框架的实现原理具有一个深刻而认识,我们采用与之类似的设计构架了一个名为Cat的DI框架.在<依赖注入[4]: 创建一个简易版的DI框架[上篇]> ...
- 依赖注入[4]: 创建一个简易版的DI框架[上篇]
本系列文章旨在剖析.NET Core的依赖注入框架的实现原理,到目前为止我们通过三篇文章(<控制反转>.<基于IoC的设计模式>和< 依赖注入模式>)从纯理论的角度 ...
随机推荐
- github拉去代码慢的处理方式(最简单)
https://github.com/xxx/xxxx 替换成 https://github.com.cnpmjs.org/xxx/xxxx 再去拉取,速度快很多,亲测可用
- vue 修改数据
通过数组中的方法改变数据 变异方法(改变原数组) push() pop() shift() unshift() splice() sort() reverse() 替换数组(生成新数组) filter ...
- Git中一个由readme.md文件引起的问题
githup中建立远程仓库时,勾选了创建readme文件,本地仓库无法push,解决方法: https://blog.csdn.net/ashencode/article/details/816249 ...
- linux 配置本地yum源,配置国内yum源,配置epel源
目录 一.配置本地yum源 二.配置国内yum源和epel源 一.配置本地yum源 1.挂载ISO镜像 mount -o loop /mnt/yum-iso/CentOS-7-x86_64-DVD-1 ...
- react第十单元(children的深入用法-React.Children对象上的方法)
第十单元(children的深入用法-React.Children对象上的方法) #课程目标 理解什么是children 掌握React.Children对象上的方法 #知识点 什么是children ...
- 多任务-python实现-进程(2.1.7)
@ 目录 1.进程是什么 2.进程的生命周期 3.Python中多进程的实现 4.进程和线程的区别 1.进程是什么 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源 ...
- Google Analytics 统计用户点击和每个页面浏览的用户id
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-805xxx-10"></sc ...
- 恶补了 Python 装饰器的六种写法,你随便问~
本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理 今天给大家分享一下关于装饰器的知识点,内容非常干,全程高能,认真吸收看完,一定会对装饰器有更深的理解 ...
- EF5中使用UnitOfWork
前言 每次提交数据库都会打开一个连接,造成结果是:多个连接无法共用一个数据库级别的事务,也就无法保证数据的原子性.一致性. 解决办法是:在ObjectContext的CRUD操作基础上再包装一层,提供 ...
- Nuget 安装本地包文件
Install-Package SomePackage -Source C:\PathToThePackageDir\