前言java
本文是「如何實現一個簡易版的 Spring」系列的第二篇,在 第一篇 介紹瞭如何實現一個基於 XML 的簡單 Setter 注入,這篇來看看要如何去實現一個簡單的Constructor 注入功能,實現步驟和 Setter 注入是同樣的「套路」,先設計一個數據結構去解析表達 XML 配置文件裏的信息,而後再使用這些解析好的數據結構作一些事情,好比這裏的 Constructor 注入。話很少說,下面咱們直接進入正題。node
數據結構設計spring
使用 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 類以下:ide
/** * @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 做爲內部類,以下所示:this
/** * @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 配置文件code
有了 上篇文章 的基礎,解析 XML 也比較簡單,這裏咱們解析的是 constructor-arg 節點,組裝數據添加到 BeanDefinition 的 ConstructorArgument 屬性中,修改 XmlBeanDefinitionReader 類的 loadBeanDefinition(Resource resource) 方法以下:blog
/** * @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 之實現字段註解方式注入」。