以前在 上篇 提到过会实现一个简易版的 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 查看完整代码。