本文是「如何实现一个简易版的 Spring」系列的第二篇,在 第一篇 介绍了如何实现一个基于 XML 的简单 Setter 注入,这篇来看看要如何去实现一个简单的 Constructor 注入功能,实现步骤和 Setter 注入是一样的“套路”,先设计一个数据结构去解析表达 XML 配置文件里的信息,然后再使用这些解析好的数据结构做一些事情,比如这里的 Constructor 注入。话不多说,下面我们直接进入正题。
数据结构设计使用 Constructor 注入方式的 XML 的一种配置如下所示:
<bean> <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 ... }