前言

本文是「如何实现一个简易版的 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介绍1

    1.1 nginx 是什么? 是一个高性能的web服务器和反向代理服务器 http://www.nginx.cn/ nginx中文手册 1.2 nginx的优点 1 支持高并发:能支持几万并发连接(特 ...

  2. 微信小程序-卡券开发(前端)

    刚完成一个微信小程序卡券开发的项目.下面记录开发前,自己困惑的几个问题. 因为我只负责了前端.所以下面主要是前端的工作. 项目概述:按照设计图开发好首页上的优惠券列表,点击某个优惠券,输入手机号,点击 ...

  3. 某宝的微信小程序源码合集

    这是我在某宝买的小程序源码合集.能用的我用不到.. 我用的到的有的有没后端.仅供个人参考,严禁侵权或商业用途! 下载地址:https://pan.baidu.com/s/1cQEQ17LdN-7hxD ...

  4. SpringBoot + Layui + JustAuth +Mybatis-plus实现可第三方登录的简单后台管理系统

    1. 简介   在之前博客:SpringBoot基于JustAuth实现第三方授权登录 和 SpringBoot + Layui +Mybatis-plus实现简单后台管理系统(内置安全过滤器)上改造 ...

  5. css样式规则

    在css样式规则中: 1.选择器用于指定CSS样式作用的HTML对象,花括号内是对该对象设置的具体样式. 2.属性和属性值以"键值对"的形式出现. 3.属性是对指定的对象设置的样式 ...

  6. 【原创】WPF TreeView带连接线样式的优化(WinFrom风格)

    一.前言 之前查找WPF相关资料的时候,发现国外网站有一个TreeView控件的样式,是WinFrom风格的,样式如下,文章链接:https://www.codeproject.com/tips/67 ...

  7. 03-flask-视图函数基础

    代码 from flask import Flask, request, url_for, jsonify, redirect # 创建Flask对象 app = Flask(__name__) @a ...

  8. matplotlib的学习8-scatter散点图

    import matplotlib.pyplot as plt import numpy as np n = 1024 # data size #生成1024个呈标准正太分布的二维数组(平均数为0,方 ...

  9. 工作流学习之 IDEA 使用activiti插件 出现乱码

    今天学习 工作流 (work flow ) 的时候遇到了一点小问题 就是在 activitit的插件的时候 出现了乱码,弄了很久,终于解决了,就做个总结 嘻嘻 当场懵了,我记得我改了编码呀 - (Se ...

  10. luabind 使用

    LuaBind --最强大的Lua C++ Bind 转载:http://www.cppblog.com/deane/articles/49208.html1 介绍LuaBind 是一个帮助你绑定C+ ...