跳至主要內容

第05章:资源加载器解析文件注册对象

AruNi_Lu框架Spring约 1452 字大约 5 分钟

本文内容

1. 设计

在上一章中,我们可以通过单元测试进行手动操作 Bean 对象的定义、注册和属性填充,以及最终获取对象调用方法。但是,实际上有个问题,在注册多个 Bean 时,如果依赖的属性很多,那么需要将这些属性一个一个地封装成 PropertyValue,再添加进PropertyValues,然后才进行属性填充,无疑增加了很多代码量。

所以这个章节,我们就用配置文件的方式,将所有的 Bean,以及 Bean 所依赖的属性都配置到 XML 文件。所以就需要一个 能解析 XML 配置文件的模块,将文件中的信息解析出来,然后由这个模块进行 PropertyValues 的处理,接着进行 Bean 的注册,此时就可以顺带把依赖的属性 PropertyValues 也传递进去。

我们读取的文件有多种,这里提供 ClassPath、File 和 远程的文件,整体设计结构如下图:

img

2. 实现

由于资源加载器属于相对独立的部分,所以可以单独定义一个包 core.io,专门用来处理资源。

主要新增了一个资源接口 Resource,提供一个获取资源对应的输入流的方法(因为读取资源需要输入流),然后有三种加载资源的方式,分别是 ClassPathResource、FileSystemResource 和 UrlResource,它们实现了 Resource 接口,实现了具体的获取对应输入流的方法。

接着,为了方便调用者使用这三种加载资源的方式,因此再抽象出一个资源加载器接口 ResourceLoader,提供一个获取资源 Resource 的方法,由该接口的实现类具体实现该方法,判断获取哪种加载资源的方式。

新增一个 DefaultResourceLoader 类,实现 ResourceLoader 接口,实现具体获取哪种加载资源的方式的方法

这样,资源加载模块就设计好了,不过现在有个问题。

如何将这个资源加载模块和 Bean 相关模块关联起来使用呢?具体来说,如果我们使用资源加载模块将配置文件中的 Bean 和依赖属性读取了出来,接着怎么将读取出来的 Bean 注册进 Bean 容器呢

毕竟现在的资源加载模块与 Bean 相关模块是分离的,注册 Bean 的能力只有 DefaultListableBeanFactory 类有(它实现了 BeanDefinitionRegistry 接口,实现了 registerBeanDefinition() 方法),不可能也让资源加载模块的类去实现 BeanDefinitionRegistry 接口吧,毕竟该模块与 Bean 无关,这样做太混乱了,污染了 BeanDefinitionRegistry 接口。

所以,我们需要在新增一个接口 BeanDefinitionReader,它提供获取 BeanDefinitionRegistry 和 ResourceLoader 的方法,这样就将这两者关联起来了。同时,该接口还提供加载 BeanDefinition 的方法 loadBeanDefinitions(),此方法在实现类中具体实现。

有了接口,当然要有实现类了,所以再新增一个 AbstractBeanDefinitionReader 抽象类,提供注册类属性和资源加载类属性,并提供了构造函数,让外部的使用者把 BeanDefinitionRegistry 传递进来,也实现了 BeanDefinitionReader 接口,实现对应返回注册类属性或资源加载类属性的方法

这样,就把资源加载模块和 Bean 相关模块关联起来了。

最后,再定义一个具体的 XML 文件解析类,用来做 具体的解析、注册 的工作,这个类就是 XmlBeanDefinitionReader,它只需要 继承 AbstractBeanDefinitionReader 类,就可以拥有资源加载模块和 Bean 相关模块的功能了

到此,所有的设计就完成了,先来看看目录结构的更变:

image-20230314205532912

类图如下:

step-05

3. 测试

UserDao 和 UserService 类和上一章相同,这里再建一个 resource 文件夹,用于存放测试需要的文件资源。

先来测试资源的加载:

  • 在 resource 文件夹中添加 important.properties 文件:

    image-20230314231607360

  • 测试类:

    @SpringBootTest(classes = ApiTest.class)
    public class ApiTest {
    
        // DefaultResourceLoader,用于获取资源
        private DefaultResourceLoader resourceLoader;
    
        @Before
        public void init() {
            resourceLoader = new DefaultResourceLoader();
        }
    
        // 下面三个方法分别测试 加载 ClassPath、FileSystem、URL 文件
        @Test
        public void test_classpath() throws IOException {
            Resource resource = resourceLoader.getResource("classpath:important.properties");
            InputStream inputStream = resource.getInputStream();
            String content = IoUtil.readUtf8(inputStream);
            System.out.println(content);
        }
    
        @Test
        public void test_file() throws IOException {
            Resource resource = resourceLoader.getResource("src/test/resources/important.properties");
            InputStream inputStream = resource.getInputStream();
            String content = IoUtil.readUtf8(inputStream);
            System.out.println(content);
        }
    
        @Test
        public void test_url() throws IOException {
            // 读取后可以从内容中搜索关键字;OLpj9823dZ
            Resource resource = resourceLoader.getResource("https://github.com/AruNi-01/small-spring/blob/main/small-spring-step-05/src/test/resources/important.properties");
            InputStream inputStream = resource.getInputStream();
            String content = IoUtil.readUtf8(inputStream);
            System.out.println(content);
        }
    }
    
  • 测试结果:均可输出文件内容。

再来测试真正的加载 XML 文件,解析 XML 文件里的属性,然后再注册 Bean 到容器:

  • 在 resource 文件夹中添加 spring.xml 文件:

    image-20230314232020080

  • 测试类:

    @Test
    public void test_xml() {
        // 1. 初始化 beanFactory
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    
        // 2. 读取配置文件 & 注册 Bean(DefaultListableBeanFactory 继承了 BeanDefinitionRegistry,所以能直接传参)
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions("classpath:spring.xml");
    
        // 3. 获取 Bean 对象调用方法
        UserService userService = (UserService) beanFactory.getBean("userService", UserService.class);
        userService.queryUserInfo();
    }
    
  • 输出:

    查询用户信息:孙悟空
    

可以发现,只需要一份配置文件,便不用我们手动添加依赖属性和注册 Bean,极大的简化了开发,可以对比上一章注册 Bean 的流程。

4. 流程

流程图如下:

上次编辑于: