mghio
读完需要
25
分钟速读仅需 9 分钟
前言
之前在 上篇 提到过会实现一个简易版的 IoC 和 AOP,今天它终于来了。相信对于使用 Java 开发语言的朋友们都使用过或者听说过 Spring 这个开发框架,绝大部分的企业级开发中都离不开它,通过官网可以了解到其生态非常庞大,针对不同方面的开发提供了一些解决方案,可以说 Spring 框架的诞生是对 Java 开发人员的一大福利,自 2004 年发布以来,Spring 为了解决一些企业开发中的痛点先后引入了很多的特性和功能,其中最重要的就是我们经常听到的 IoC 和 AOP 特性,由于涉及到的知识和细节比较多,会分为几篇文章来介绍,今天这篇(也是第一篇)我们来看看如何实现基于 XML 配置方式的 Setter 注入。
预备知识
既然是通过 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 配置文件中的标签解析为 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(BeanDefinitionExcep