共计 9986 个字符,预计需要花费 25 分钟才能阅读完成。
前言
之前在 上篇 提到过会实现一个简易版的 IoC
和 AOP
,明天它终于来了。。。置信对于应用 Java
开发语言的敌人们都应用过或者据说过 Spring
这个开发框架,绝大部分的企业级开发中都离不开它,通过 官网 能够理解到其生态十分宏大,针对不同方面的开发提供了一些解决方案,能够说 Spring
框架的诞生是对 Java
开发人员的一大福利,自 2004
年公布以来,Spring
为了解决一些企业开发中的痛点先后引入了很多的个性和性能,其中最重要的就是咱们常常听到的 IoC
和 AOP
个性,因为波及到的常识和细节比拟多,会分为几篇文章来介绍,明天这篇(也是第一篇)咱们来看看如何实现基于 XML
配置形式的 Setter 注入 。
<!–more–>
准备常识
既然是通过 XML
配置文件的形式,首先第一件事就是要读取 XML
文件而后转换为咱们须要的数据结构,解析 XML
文件有但不限于这些形式(JDOM、XOM、dom4j),这里应用的是简略易上手的 dom4j,所你得对其基础知识有一些简略理解,其实都是一些很简略的办法根底应用而已,第二个就是你要有一些 Spring
框架的应用教训,这里实现的简易版实质上是对 Spring
的一个精简后的外围局部的简略实现,是的,没错,你只须要有了这些根底准备常识就能够了。
根底数据结构形象
在开始编码实现前先要做一些简略的构思和设计,首先在 Spring
中把一个被其治理的对象称之为 Bean
,而后其它的操作都是围绕这个 Bean
来开展设计的,所以为了能在程序中对立并且标准的示意一个 Bean
的定义,于是第一个接口 BeanDefinition
就进去了,本次须要的一些根本信息蕴含 Bean
的名称、所属类名称、是否单例、作用域等,如下所示:
当初 BeanDefinition
有了,接下来就是要依据这个 BeanDefinition
去创立出对应的 Bean
实例了,很显然这须要一个 Factory
工厂接口去实现这个创立的工作,这个创立 Bean
的接口命名为 BeanFactory
,其提供依据不同条件去创立绝对应的 Bean
实例性能(比方 beanId
),然而创立的前提是须要先注册这个 BeanDefinition
,而后依据肯定条件再从中去获取 BeanDefinition
,依据 繁多职责 准则,这个性能应该由一个新的接口去实现,次要是做注册和获取 BeanDefinition
的工作,故将其命名为 BeanDefinitionRegistry
,咱们须要的 BeanDefinition
要从哪里获取呢?很显然咱们是基于 XML
配置的形式,当然是从 XML
配置文件中获取到的,同样依据繁多职责准则,也须要一个类去实现这个事件,将其命名为 XMLBeanDefinitionReader
,这部分的整体构造如下所示:
接下来面临的一个问题就是,像 XML
这种配置文件资源要如何示意呢,这些配置对于程序来说是一种资源,能够对立形象为 Resource
,而后提供一个返回资源对应流(InputStream
)对象接口,这种资源能够从我的项目中获取、本地文件获取甚至是从近程获取,它们都是一种 Resource
,构造如下:
最初就是要一个提供去组合调用下面的那些类去实现 XML
配置文件解析为 BeanDefinition
并注入到容器中了的性能,负责这程序上下文的职责,将其命名为 ApplicationContext
,这里同样也能够依据 Resource
的类型分为多种不同的类,比方:FileSystmXmlApplicationContext
、ClassPathXmlApplicationContext
等,这些外部都有一个将配置文件转换为 Resource
的过程,能够应用 模板办法 形象出一个公共父类抽象类,如下所示:
总结以上剖析后果,得出初步类图设计如下:
最终要实现 Setter
注入这个指标,能够将其合成为以下两个步骤:
- 将
XML
配置文件中的<bean>
标签解析为BeanDefinition
并注入到容器中 - 实现
Setter
注入
上面咱们分为这两个局部来别离讲述如何实现。
配置文件解析
假如有如下内容的配置文件 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
注入。
如何实现 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 查看残缺代码。