前言
本文是「如何实现一个简易版的 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
的过程整体上分为两步,第一步在遍历每个 <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 – 实现字段注解形式注入」。