以前在 上篇 提到過會實現一個簡易版的 IoC
和 AOP
,今天它終於來了。。。相信對於使用 Java
開發語言的朋友們都使用過或者據說過 Spring
這個開發框架,絕大部分的企業級開發中都離不開它,經過 官網 能夠了解到其生態很是龐大,針對不一樣方面的開發提供了一些解決方案,能夠說 Spring
框架的誕生是對 Java
開發人員的一大福利,自 2004
年發佈以來,Spring
爲了解決一些企業開發中的痛點前後引入了不少的特性和功能,其中最重要的就是咱們常常聽到的 IoC
和 AOP
特性,因爲涉及到的知識和細節比較多,會分爲幾篇文章來介紹,今天這篇(也是第一篇)咱們來看看如何實現基於 XML
配置方式的 Setter 注入。html
<!--more-->git
既然是經過 XML
配置文件的方式,首先第一件事就是要讀取 XML
文件而後轉換爲咱們須要的數據結構,解析 XML
文件有但不限於這些方式(JDOM、XOM、dom4j),這裏使用的是簡單易上手的 dom4j,所你得對其基礎知識有一些簡單瞭解,其實都是一些很簡單的方法基礎使用而已,第二個就是你要有一些 Spring
框架的使用經驗,這裏實現的簡易版本質上是對 Spring
的一個精簡後的核心部分的簡單實現,是的,沒錯,你只須要有了這些基礎預備知識就能夠了。github
在開始編碼實現前先要作一些簡單的構思和設計,首先在 Spring
中把一個被其管理的對象稱之爲 Bean
,而後其它的操做都是圍繞這個 Bean
來展開設計的,因此爲了能在程序中統一而且規範的表示一個 Bean
的定義,因而第一個接口 BeanDefinition
就出來了,本次須要的一些基本信息包含 Bean
的名稱、所屬類名稱、是否單例、做用域等,以下所示:spring
如今 BeanDefinition
有了,接下來就是要根據這個 BeanDefinition
去建立出對應的 Bean
實例了,很顯然這須要一個 Factory
工廠接口去完成這個建立的工做,這個建立 Bean
的接口命名爲 BeanFactory
,其提供根據不一樣條件去建立相對應的 Bean
實例功能(好比 beanId
),可是建立的前提是須要先註冊這個 BeanDefinition
,而後根據必定條件再從中去獲取 BeanDefinition
,根據 單一職責 原則,這個功能應該由一個新的接口去完成,主要是作註冊和獲取 BeanDefinition
的工做,故將其命名爲 BeanDefinitionRegistry
,咱們須要的 BeanDefinition
要從哪裏獲取呢?很顯然咱們是基於 XML
配置的方式,固然是從 XML
配置文件中獲取到的,一樣根據單一職責原則,也須要一個類去完成這個事情,將其命名爲 XMLBeanDefinitionReader
,這部分的總體結構以下所示:apache
接下來面臨的一個問題就是,像 XML
這種配置文件資源要如何表示呢,這些配置對於程序來講是一種資源,能夠統一抽象爲 Resource
,而後提供一個返回資源對應流(InputStream
)對象接口,這種資源能夠從項目中獲取、本地文件獲取甚至是從遠程獲取,它們都是一種 Resource
,結構以下:緩存
最後就是要一個提供去組合調用上面的那些類去完成 XML
配置文件解析爲 BeanDefinition
並注入到容器中了的功能,擔任這程序上下文的職責,將其命名爲 ApplicationContext
,這裏一樣也能夠根據 Resource
的類型分爲多種不一樣的類,好比:FileSystmXmlApplicationContext
、ClassPathXmlApplicationContext
等,這些內部都有一個將配置文件轉換爲 Resource
的過程,可使用 模板方法 抽象出一個公共父類抽象類,以下所示:數據結構
總結以上分析結果,得出初步類圖設計以下:app
最終要實現 Setter
注入這個目標,能夠將其分解爲如下兩個步驟:框架
XML
配置文件中的 <bean>
標籤解析爲 BeanDefinition
並注入到容器中Setter
注入下面咱們分爲這兩個部分來分別講述如何實現。dom
假設有以下內容的配置文件 applicationcontext-config1.xml
:
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.e3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="orderService" class="cn.mghio.service.version1.OrderService" /> </beans>
最終須要解析出一個 id
爲 orderService
類型爲 cn.mghio.service.version1.OrderService
的 BeanDefinition
,翻譯成測試類的話也就是須要讓以下測試類能夠運行經過:
/** * @author mghio */ public class BeanFactoryTest { private Resource resource; private DefaultBeanFactory beanFactory; private XmlBeanDefinitionReader reader; @BeforeEach public void beforeEach() { resource = new ClassPathResource("applicationcontext-config1.xml"); beanFactory = new DefaultBeanFactory(); reader = new XmlBeanDefinitionReader(beanFactory); } @Test public void testGetBeanFromXmlFile() { reader.loadBeanDefinition(resource); BeanDefinition bd = beanFactory.getBeanDefinition("orderService"); assertEquals("cn.mghio.service.version1.OrderService", bd.getBeanClassNam()); OrderService orderService = (OrderService) beanFactory.getBean("orderService"); assertNotNull(orderService); } @Test public void testGetBeanFromXmlFileWithInvalidBeanId() { assertThrows(BeanCreationException.class, () -> beanFactory.getBean("notExistsBeanId")); } @Test public void testGetFromXmlFilWithFileNotExists() { resource = new ClassPathResource("notExists.xml"); assertThrows(BeanDefinitionException.class, () -> reader.loadBeanDefinition(resource)); } }
能夠看到這裏面的關鍵就是如何去實現 XmlBeanDefinitionReader
類的 loadBeanDefinition
從配置中加載和注入 BeanDefinition
,思考分析後否則發現這裏主要是兩步,第一步是解析 XML
配置轉換爲 BeanDefinition
,這就須要上文提到的 dom4j
提供的能力了,第二步將解析出來的 BeanDefinition
注入到容器中,經過組合使用 BeanDefinitionRegistry
接口提供註冊 BeanDefinition
的能力來完成。讀取 XML
配置的類 XmlBeanDefinitionReader
的代碼實現很快就能夠寫出來了,該類部分代碼以下所示:
/** * @author mghio */ public class XmlBeanDefinitionReader { private static final String BEAN_ID_ATTRIBUTE = "id"; private static final String BEAN_CLASS_ATTRIBUTE = "class"; private BeanDefinitionRegistry registry; public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) { this.registry = registry; } @SuppressWarnings("unchecked") 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); this.registry.registerBeanDefinition(beanId, bd); } } catch (DocumentException | IOException e) { throw new BeanDefinitionException("IOException parsing XML document:" + configurationFile, e); } } }
而後當調用 BeanFactory
的 getBean
方法時就能夠根據 Bean
的全限定名建立一個實例出來了(PS:暫時不考慮實例緩存),方法實現主要代碼以下:
public Object getBean(String beanId) { BeanDefinition bd = getBeanDefinition(beanId); if (null == bd) { throw new BeanCreationException("BeanDefinition does not exists, beanId:" + beanId); } ClassLoader classLoader = this.getClassLoader(); String beanClassName = bd.getBeanClassNam(); try { Class<?> clazz = classLoader.loadClass(beanClassName); return clazz.newInstance(); } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { throw new BeanCreationException("Created bean for " + beanClassName + " fail.", e); } }
到這裏配置文件解析方面的工做已完成,接下來看看要如何實現 Setter
注入。
首先實現基於 XML
配置文件的 Setter
注入本質上也是解析 XML
配置文件,而後再調用對象屬性的 setXXX
方法將配置的值設置進去,配置文件 applicationcontext-config2.xml
以下所示:
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.e3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="stockDao" class="cn.mghio.dao.version2.StockDao"/> <bean id="tradeDao" class="cn.mghio.dao.version2.TradeDao"/> <bean id="orderService" class="cn.mghio.service.version2.OrderService"> <property name="stockDao" ref="stockDao"/> <property name="tradeDao" ref="tradeDao"/> <property name="num" value="2"/> <property name="owner" value="mghio"/> <property name="orderTime" value="2020-11-24 18:42:32"/> </bean> </beans>
咱們以前使用了 BeanDefinition
去抽象了 <bean>
標籤,這裏面臨的第一個問題就是要如何去表達配置文件中的 <property>
標籤,其中 ref
屬性表示一個 beanId
、value
屬性表示一個值(值類型爲:Integer
、String
、Date
等)。觀察後能夠發現,<property>
標籤本質上是一個 K-V
格式的數據(name
做爲 Key
,ref
和 value
做爲 Value
),將這個類命名爲 PropertyValue
,很明顯一個 BeanDefinition
會有多個 PropertyValue
,結構以下:
這裏的 value
有兩種不一樣的類型,一種是表示 Bean
的 id
值,運行時會解析爲一個 Bean
的引用,將其命名爲 RuntimeBeanReference
,還有一種是 String
類型,運行時會解析爲不一樣的類型,將其命名爲 TypeStringValue
。第二個問題就是要如何將一個類型轉換爲另外一個類型呢?好比將上面配置中的字符串 2
轉換爲整型的 2
、字符串 2020-11-24 18:42:32
轉換爲日期,這類通用的問題前輩們已經開發好了類庫處理了,這裏咱們使用 commons-beanutils 庫提供的 BeanUtils.copyProperty(final Object bean, final String name, final Object value)
方法便可。而後只需在以前 XmlBeanDefinitionReader
類的 loadBeanDefinition
方法解析 XML
配置文件的時解析 <bean>
標籤下的 <property>
標籤並設置到 BeanDefinition
的 propertyValues
屬性中;DefaultBeanFactory
中的 getBean
方法分爲實例化 Bean
和讀取向實例化完成的 Bean
使用 Setter
注入配置文件中配置屬性對應的值。XmlBeanDefinitionReader
的 loadBeanDefinition()
方法代碼修改成:
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); parsePropertyElementValue(element, bd); // parse <property> this.registry.registerBeanDefinition(beanId, bd); } } catch (DocumentException | IOException e) { throw new BeanDefinitionException("IOException parsing XML document:" + resource, e); } } private void parsePropertyElementValue(Element element, BeanDefinition bd) { Iterator<Element> iterator = element.elementIterator(PROPERTY_ATTRIBUTE); while (iterator.hasNext()) { Element propertyElement = iterator.next(); String propertyName = propertyElement.attributeValue(NAME_ATTRIBUTE); if (!StringUtils.hasText(propertyName)) { return; } Object value = parsePropertyElementValue(propertyElement, propertyName); PropertyValue propertyValue = new PropertyValue(propertyName, value); bd.getPropertyValues().add(propertyValue); } } private Object parsePropertyElementValue(Element propertyElement, String propertyName) { String elementName = (propertyName != null) ? "<property> element for property '" + propertyName + "' " : "<constructor-arg> element"; boolean hasRefAttribute = propertyElement.attribute(REF_ATTRIBUTE) != null; boolean hasValueAttribute = propertyElement.attribute(VALUE_ATTRIBUTE) != null; if (hasRefAttribute) { String refName = propertyElement.attributeValue(REF_ATTRIBUTE); RuntimeBeanReference ref = new RuntimeBeanReference(refName); return ref; } else if (hasValueAttribute) { String value = propertyElement.attributeValue(VALUE_ATTRIBUTE); TypedStringValue valueHolder = new TypedStringValue(value); return valueHolder; } else { throw new RuntimeException(elementName + " must specify a ref or value"); } }
DefaultBeanFactory
的 getBean
方法也增長 Bean
屬性注入操做,部分代碼以下:
public Object getBean(String beanId) { BeanDefinition bd = getBeanDefinition(beanId); // 1. instantiate bean Object bean = instantiateBean(bd); // 2. populate bean populateBean(bd, bean); return bean; } private Object instantiateBean(BeanDefinition bd) { ClassLoader classLoader = this.getClassLoader(); String beanClassName = bd.getBeanClassName(); try { Class<?> clazz = classLoader.loadClass(beanClassName); return clazz.newInstance(); } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { throw new BeanCreationException("Created bean for " + beanClassName + " fail.", e); } } private void populateBean(BeanDefinition bd, Object bean) { List<PropertyValue> propertyValues = bd.getPropertyValues(); if (propertyValues == null || propertyValues.isEmpty()) { return; } BeanDefinitionResolver resolver = new BeanDefinitionResolver(this); SimpleTypeConverted converter = new SimpleTypeConverted(); try { for (PropertyValue propertyValue : propertyValues) { String propertyName = propertyValue.getName(); Object originalValue = propertyValue.getValue(); Object resolvedValue = resolver.resolveValueIfNecessary(originalValue); BeanUtils.copyProperty(bean, propertyName, resolvedValue); } } catch (Exception e) { throw new BeanCreationException("Failed to obtain BeanInfo for class [" + bd.getBeanClassName() + "]"); } }
至此,簡單的 Setter
注入功能已完成。
本文簡單概述了基於 XML
配置文件方式的 Setter
注入簡單實現過程,總體實現 Setter
注入的思路就是先設計一個數據結構去表達 XML
配置文件中的標籤數據(好比上面的 PropertyValue
),而後再解析配置文件填充數據並利用這個數據結構完成一些功能(好比 Setter 注入等
)。感興趣的朋友能夠到這裏 mghio-spring 查看完整代碼。