手写Spring:第6章-资源加载器解析文件注册对象

文章目录

  • 一、目标:资源加载器解析文件注册对象
  • 二、设计:资源加载器解析文件注册对象
  • 三、实现:资源加载器解析文件注册对象
    • [3.1 工程结构](#3.1 工程结构)
    • [3.2 资源加载器解析文件注册对象类图](#3.2 资源加载器解析文件注册对象类图)
    • [3.3 类工具类](#3.3 类工具类)
    • [3.4 资源加载接口定义和实现](#3.4 资源加载接口定义和实现)
      • [3.4.1 定义资源加载接口](#3.4.1 定义资源加载接口)
      • [3.4.2 类路径资源实现接口](#3.4.2 类路径资源实现接口)
      • [3.4.3 文件资源实现类](#3.4.3 文件资源实现类)
      • [3.4.4 URL资源实现类](#3.4.4 URL资源实现类)
    • [3.5 包装资源加载器](#3.5 包装资源加载器)
      • [3.5.1 资源加载器接口](#3.5.1 资源加载器接口)
      • [3.5.2 默认实现的资源加载器](#3.5.2 默认实现的资源加载器)
    • [3.6 Bean定义读取接口及实现类](#3.6 Bean定义读取接口及实现类)
      • [3.6.1 定义Bean读取接口](#3.6.1 定义Bean读取接口)
      • [3.6.2 定义Bean读取抽象类](#3.6.2 定义Bean读取抽象类)
      • [3.6.3 解析XML处理Bean注册](#3.6.3 解析XML处理Bean注册)
    • [3.7 Bean工厂接口完善](#3.7 Bean工厂接口完善)
      • [3.7.1 Bean工厂接口](#3.7.1 Bean工厂接口)
      • [3.7.2 扩展Bean工厂子接口](#3.7.2 扩展Bean工厂子接口)
      • [3.7.3 扩展Bean工厂的层次子接口](#3.7.3 扩展Bean工厂的层次子接口)
      • [3.7.4 自动化处理Bean工厂配置接口](#3.7.4 自动化处理Bean工厂配置接口)
      • [3.7.5 Bean工厂配置化接口](#3.7.5 Bean工厂配置化接口)
      • [3.7.6 Bean工厂预先实例化的操作接口](#3.7.6 Bean工厂预先实例化的操作接口)
    • [3.8 Bean接口及其实现类完善](#3.8 Bean接口及其实现类完善)
      • [3.8.1 Bean工厂抽象类](#3.8.1 Bean工厂抽象类)
      • [3.8.2 修改Bean注册接口](#3.8.2 修改Bean注册接口)
      • [3.8.3 默认的Bean工厂实现类](#3.8.3 默认的Bean工厂实现类)
  • 四、测试:资源加载器解析文件注册对象
    • [4.1 添加配置文件](#4.1 添加配置文件)
      • [4.1.1 properties配置文件](#4.1.1 properties配置文件)
      • [4.1.2 xml配置文件](#4.1.2 xml配置文件)
    • [4.2 单元测试](#4.2 单元测试)
      • [4.2.1 xml配置测试](#4.2.1 xml配置测试)
      • [4.2.2 加载类路径测试](#4.2.2 加载类路径测试)
      • [4.2.3 加载文件路径测试](#4.2.3 加载文件路径测试)
      • [4.2.4 加载URL路径测试](#4.2.4 加载URL路径测试)
  • 五、总结:资源加载器解析文件注册对象

一、目标:资源加载器解析文件注册对象

💡 如何通过 Spring 配置文件的方式将 Bean 对象实例化?

  • 现在是通过单元测试手动操作 Beab 对象的定义、注册和属性填充,以及最终获取对象调用方法。
    • 但是这里有个问题?实际使用 Spring 框架,是不太可能让用户通过手动方式创建的,而是最好能通过配置文件的方式简化创建过程。
  • 如图中所示:把 2、3、4 整合到 Spring 框架中,通过 Spring 配置文件的方式将 Bean 对象实例化。
  • 接下来就需要在现有的 Spring 框架中,添加能解决 Spring 配置的读取、解析、注册 Bean 操作。

二、设计:资源加载器解析文件注册对象

💡 技术设计:资源加载器解析文件注册对象

  • 需要在现有的 Spring 框架雏形中添加一个资源解析器,也就是能读取 classpath、本地文件和云文件的配置内容。
    • 这些配置内容就是像使用 Spring 时配置的 spring.xml 一样,里面会包括 Bean 对象的描述和属性信息。
    • 在读取配置文件信息后,接下来就是对配置文件中的 Bean 描述信息解析后进行注册操作,把 Bean 对象注册到 Spring 容器中。
  • 资源加载器属于相对独立的部分,它位于 Spring 框架核心包下的 I/O 实现内容,主要用于处理 Class、本地和云环境的文件信息。
  • 当资源可以加载后,接下来就是解析和注册 BeanSpring 中的操作,这部分实现需要和 DefaultListableBeanFactory 核心类结合起来。
    • 因为你所有的解析后的注册动作,都会把 Bean 定义信息放入到这个类中。
  • 在实现时就设计好接口的实现层级关系,包括我们需要定义出 Bean 定义的读取接口 BeanDefinitionReader 以及做好对应的实现类,在实现类中完成对 Bean 对象的解析和注册。

三、实现:资源加载器解析文件注册对象

3.1 工程结构

java 复制代码
spring-step-05
|-src
  |-main
  | |-java
  |   |-com.lino.springframework
  |     |-beans
  |     | |-factory
  |     | | |-config
  |     | | | |-AutowireCapableBeanFactory.java
  |     | | | |-BeanDefinition.java
  |     | | | |-BeanReference.java
  |     | | | |-ConfigurableBeanFactory.java
  |     | | | |-SingletonBeanRegistry.java
  |     | | |-support
  |     | | | |-AbstractAutowireCapableBeanFactory.java
  |     | | | |-AbstractBeabDefinitionReader.java
  |     | | | |-AbstractBeabFactory.java
  |     | | | |-BeabDefinitionReader.java
  |     | | | |-BeanDefinitionRegistry.java
  |     | | | |-CglibSubclassingInstantiationStrategy.java
  |     | | | |-DefaultListableBeanFactory.java
  |     | | | |-DefaultSingletonBeanRegistry.java
  |     | | | |-InstantiationStrategy.java
  |     | | | |-SimpleInstantiationStrategy.java
  |     | | |-support
  |     | | | |-XMLBeanDefinitionReader.java
  |     | | |-BeanFactory.java
  |     | | |-ConfigurableListableBeanFactory.java
  |     | | |-HierarcgicalBeanFactory.java
  |     | | |-ListableBeanFactory.java
  |     | |-BeansException.java
  |     | |-PropertyValue.java
  |     | |-PropertyValues.java
  |     |-core.io
  |     | |-ClassPathResource.java
  |     | |-DefaultResourceLoader.java
  |     | |-FileSystemResource.java
  |     | |-Resource.java
  |     | |-ResourceLoader.java
  |     | |-UrlResource.java
  |     |-util
  |     | |-ClassUtils.java
  |-test
    |-java
      |-com.lino.springframework.test
      |-bean
      | |-UserDao.java
      | |-UserService.java
      |-ApiTest.java
    |-resources
      |-important.properties
      |-spring.xml

3.2 资源加载器解析文件注册对象类图

  • 为了能把 Bean 的定义、注册和初始化交给 spring.xml 配置化处理,那么就需要实现两大块内容,分别是:资源加载器、xml 资源处理类。
    • 实现过程主要以对接口 ResourceReourceLoader 的实现。
    • 而另外 BeanDefinitionReader 接口则是对资源的具体使用,将配置信息注册到 Spring 容器中去。
  • Resource 的资源加载器的实现中包括了:ClassPath、系统文件、云配置文件,这三部分与 Spring 源码中的设计和实现保持一致。
    • 最终在 DefaultResourceLoader 中做具体的调用。
  • 接口:BeanDefinitionReader,实现类:AbstractBeanDefinitionReader,实现类:XMLBeanDefinitionReader
    • 这三部分内容主要是合理清晰的处理了资源读取后的注册 Bean 容器操作。
    • 接口管定义、抽象类处理非接口功能外的注册 Bean 组件填充,最终实现类即可只关心具体的业务实现
  • BeanFactory:已经存在的 Bean 工厂接口用于获取 Bean 对象。
    • 这次新增加了按照类型获取 Bean 的方法:<T> T getBean(String name, Class<T> requiredType)
  • ListableBeanFactory:是一个扩展 Bean 工厂接口的接口,新增加了 getBeansOfTypegetBeanDefinitionNames 方法。
  • HierarchicalBeanFactory:在 Spring 源码中它提供了可以获取父类 BeanFactory 方法,属于是一种扩展工厂的层次子接口
  • AutowireCapableBeanFactory:是一个自动化处理 Bean 工厂配置的接口。
  • ConfigurableBeanFactory:可获取 BeanPostProcessorBeanClassLoader 等的一个配置化接口
  • ConfigurableListableBeanFactory:提供分析和修改 Bean 以及预先实例化的操作接口。先在只有 getBeanDefinition 方法。

3.3 类工具类

ClassUtils

java 复制代码
package com.lino.springframework.util;

/**
 * @description: 类工具类
 */
public class ClassUtils {

    /**
     * 获取默认类加载器
     * @return 类加载器
     */
    public static ClassLoader getDefaultClassLoader() {
        ClassLoader cl = null;
        try {
            cl = Thread.currentThread().getContextClassLoader();
        } catch (Throwable ex) {
            // Cannot access thread context ClassLoader - falling back to system class loader...
        }
        if (cl == null) {
            // No thread context class loader -> use class loader of this class.
            cl = ClassUtils.class.getClassLoader();
        }
        return cl;
    }
}

3.4 资源加载接口定义和实现

3.4.1 定义资源加载接口

Resource.java

java 复制代码
package com.lino.springframework.core.io;

import java.io.IOException;
import java.io.InputStream;

/**
 * @description: 资源处理接口
 */
public interface Resource {

    /**
     * 加载资源流
     *
     * @return 输入流
     * @throws IOException IO异常
     */
    InputStream getInputStream() throws IOException;
}
  • Spring 框架下创建 core.io 核心包,在这个包中主要用于处理资源加载流。
  • 定义 Resource 接口,提供获取 InputStream 流的方法,接下来再分别实现三种不同的流文件操作:classPath、FileSystem、URL

3.4.2 类路径资源实现接口

ClassPathResource.java

java 复制代码
package com.lino.springframework.core.io;

import cn.hutool.core.lang.Assert;
import com.lino.springframework.util.ClassUtils;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

/**
 * @description: 类路径资源
 */
public class ClassPathResource implements Resource {

    private final String path;

    private ClassLoader classLoader;

    public ClassPathResource(String path) {
        this(path, (ClassLoader) null);
    }

    public ClassPathResource(String path, ClassLoader classLoader) {
        Assert.notNull(path, "Path must not be null");
        this.path = path;
        this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
    }

    @Override
    public InputStream getInputStream() throws IOException {
        InputStream is = classLoader.getResourceAsStream(path);
        if (is == null) {
            throw new FileNotFoundException(this.path + " cannot be opened because it does not exist");
        }
        return is;
    }
}
  • 这部分的实现是用于通过 ClassLoader 读取 ClassPath 下的文件信息,具体的读取过程:classLoader.getResourceAsStream(path)

3.4.3 文件资源实现类

FileSystemResource.java

java 复制代码
package com.lino.springframework.core.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * @description: 文件资源
 */
public class FileSystemResource implements Resource {

    private final File file;
    private final String path;

    public FileSystemResource(File file) {
        this.file = file;
        this.path = file.getPath();
    }

    public FileSystemResource(String path) {
        this.file = new File(path);
        this.path = path;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return new FileInputStream(this.file);
    }

    public final String getPath() {
        return this.path;
    }
}
  • 通过指定文件路径的方式读取文件信息。

3.4.4 URL资源实现类

UrlResource.java

java 复制代码
package com.lino.springframework.core.io;

import cn.hutool.core.lang.Assert;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;

/**
 * @description: URL 资源
 */
public class UrlResource implements Resource {

    private final URL url;

    public UrlResource(URL url) {
        Assert.notNull(url, "URL must not be null");
        this.url = url;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        URLConnection con = this.url.openConnection();
        try {
            return con.getInputStream();
        } catch (IOException ex) {
            if (con instanceof HttpURLConnection) {
                ((HttpURLConnection) con).disconnect();
            }
            throw ex;
        }
    }
}
  • 通过 HTTP 的方式读取云服务的文件。

3.5 包装资源加载器

💡 按照资源加载的不同方式,资源加载器可以把这些方式集中到统一的类服务下进行处理,外部用户只需要传递资源地址即可,简化使用。

3.5.1 资源加载器接口

ResourceLoader.java

java 复制代码
package com.lino.springframework.core.io;

/**
 * @description: 资源加载器
 */
public interface ResourceLoader {

    /**
     * Pseudo URL prefix for loading from the class path: "classpath:"
     */
    String CLASSPATH_URL_OREFIX = "classpath:";

    /**
     * 获取资源
     *
     * @param location 资源名称
     * @return 资源
     */
    Resource getResource(String location);
}
  • 定义获取资源接口,里面传递 location 地址即可。

3.5.2 默认实现的资源加载器

DefaultResourceLoader.java

java 复制代码
package com.lino.springframework.core.io;

import cn.hutool.core.lang.Assert;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * @description: 默认实现的资源处理器
 */
public class DefaultResourceLoader implements ResourceLoader {

    @Override
    public Resource getResource(String location) {
        Assert.notNull(location, "Location must not be null");
        if (location.startsWith(CLASSPATH_URL_OREFIX)) {
            return new ClassPathResource(location.substring(CLASSPATH_URL_OREFIX.length()));
        } else {
            try {
                URL url = new URL(location);
                return new UrlResource(url);
            } catch (MalformedURLException e) {
                return new FileSystemResource(location);
            }
        }
    }
}
  • 在获取资源的实现中,主要把三种不同类型的资源处理方式进行了包装,分为:判断是否为 ClassPathURL、文件。
  • 虽然 DefaultResourceLoader 类实现的过程简单,但这也是设计模式约定的具体结果,像是这里不会让外部调用方知道太多的细节,而是仅关心具体调用结果即可。

3.6 Bean定义读取接口及实现类

3.6.1 定义Bean读取接口

BeanDefinitionReader.java

java 复制代码
package com.lino.springframework.beans.factory.support;

import com.lino.springframework.beans.BeansException;
import com.lino.springframework.core.io.Resource;
import com.lino.springframework.core.io.ResourceLoader;

/**
 * @description: Bean定义读取接口
 */
public interface BeanDefinitionReader {

    /**
     * 获取bean对象注册对象
     *
     * @return bean对象注册对象
     */
    BeanDefinitionRegistry getRegistry();

    /**
     * 获取资源加载器
     *
     * @return 资源加载器
     */
    ResourceLoader getResourceLoader();

    /**
     * 加载bean定义方法
     *
     * @param resource 资源
     * @throws BeansException bean异常
     */
    void loadBeanDefinitions(Resource resource) throws BeansException;

    /**
     * 加载bean定义方法
     *
     * @param resources 资源列表
     * @throws BeansException bean异常
     */
    void loadBeanDefinitions(Resource... resources) throws BeansException;

    /**
     * 加载bean定义方法
     *
     * @param location 路径名称
     * @throws BeansException bean异常
     */
    void loadBeanDefinitions(String location) throws BeansException;
}
  • 这是一个简单 Bean 读取接口,这个接口里面定义了几个方法,包括:getRegistrygetResourceLoader,以及三个加载 Bean 定义的方法。
  • 这里需要注意 getRegistrygetResourceLoader,都是用于提供给后面三个方法的工具,加载和注册。
    • 这两个方法的实现会包装到抽象类中,以免污染具体的接口实现方法。

3.6.2 定义Bean读取抽象类

AbstractBeanDefinitionReader.java

java 复制代码
package com.lino.springframework.beans.factory.support;

import com.lino.springframework.core.io.DefaultResourceLoader;
import com.lino.springframework.core.io.ResourceLoader;

/**
 * @description: Bean定义读取抽象类
 */
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {

    private final BeanDefinitionRegistry registry;

    private ResourceLoader resourceLoader;

    protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
        this(registry, new DefaultResourceLoader());
    }

    public AbstractBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
        this.registry = registry;
        this.resourceLoader = resourceLoader;
    }

    @Override
    public BeanDefinitionRegistry getRegistry() {
        return registry;
    }

    @Override
    public ResourceLoader getResourceLoader() {
        return resourceLoader;
    }
}
  • 抽象类把 BeanDefinitionReader 接口的前两个方法全部实现了,并提供了构造函数,让外部的调用使用方,把 Bean 定义注入类,传递进来。
  • 这样在接口 BeanDefinitionReader 的具体实现类中,就可以把解析后的 XML 文件中的 Bean 信息,注册到 Spring 容器去了。

3.6.3 解析XML处理Bean注册

XMLBeanDefinitionReader.java

java 复制代码
package com.lino.springframework.beans.factory.xml;

import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.XmlUtil;
import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.PropertyValue;
import com.lino.springframework.beans.factory.config.BeanDefinition;
import com.lino.springframework.beans.factory.config.BeanReference;
import com.lino.springframework.beans.factory.support.AbstractBeanDefinitionReader;
import com.lino.springframework.beans.factory.support.BeanDefinitionRegistry;
import com.lino.springframework.core.io.Resource;
import com.lino.springframework.core.io.ResourceLoader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import java.io.IOException;
import java.io.InputStream;

/**
 * @description: XML处理Bean注册
 */
public class XMLBeanDefinitionReader extends AbstractBeanDefinitionReader {

    public XMLBeanDefinitionReader(BeanDefinitionRegistry registry) {
        super(registry);
    }

    public XMLBeanDefinitionReader(BeanDefinitionRegistry registry, ResourceLoader resourceLoader) {
        super(registry, resourceLoader);
    }

    @Override
    public void loadBeanDefinitions(Resource resource) throws BeansException {
        try {
            try (InputStream inputStream = resource.getInputStream()) {
                doLoadBeanDefinitions(inputStream);
            }
        } catch (IOException | ClassNotFoundException e) {
            throw new BeansException("IOException parsing XML document from " + resource, e);
        }
    }

    @Override
    public void loadBeanDefinitions(Resource... resources) throws BeansException {
        for (Resource resource : resources) {
            loadBeanDefinitions(resource);
        }
    }

    @Override
    public void loadBeanDefinitions(String location) throws BeansException {
        ResourceLoader resourceLoader = getResourceLoader();
        Resource resource = resourceLoader.getResource(location);
        loadBeanDefinitions(resource);
    }

    protected void doLoadBeanDefinitions(InputStream inputStream) throws ClassNotFoundException {
        Document doc = XmlUtil.readXML(inputStream);
        Element root = doc.getDocumentElement();
        NodeList childNodes = root.getChildNodes();

        for (int i = 0; i < childNodes.getLength(); i++) {
            // 判断元素
            if (!(childNodes.item(i) instanceof Element)) {
                continue;
            }
            // 判断对象
            if (!"bean".equals(childNodes.item(i).getNodeName())) {
                continue;
            }

            // 解析标签
            Element bean = (Element) childNodes.item(i);
            String id = bean.getAttribute("id");
            String name = bean.getAttribute("name");
            String className = bean.getAttribute("class");
            // 获取 Class, 方便获取类中的名称
            Class<?> clazz = Class.forName(className);
            // 优先级 id > name
            String beanName = StrUtil.isNotEmpty(id) ? id : name;
            if (StrUtil.isEmpty(beanName)) {
                beanName = StrUtil.lowerFirst(clazz.getSimpleName());
            }

            // 定义bean
            BeanDefinition beanDefinition = new BeanDefinition(clazz);
            // 读取属性并填充
            for (int j = 0; j < bean.getChildNodes().getLength(); j++) {
                // 判断元素
                if (!(bean.getChildNodes().item(j) instanceof Element)) {
                    continue;
                }
                // 判断对象
                if (!"property".equals(bean.getChildNodes().item(j).getNodeName())) {
                    continue;
                }
                // 解析标签:property
                Element property = (Element) bean.getChildNodes().item(j);
                String attrName = property.getAttribute("name");
                String attrValue = property.getAttribute("value");
                String attrRef = property.getAttribute("ref");
                // 获取属性值:引入对象、值对象
                Object value = StrUtil.isNotEmpty(attrRef) ? new BeanReference(attrRef) : attrValue;
                // 创建属性信息
                PropertyValue propertyValue = new PropertyValue(attrName, value);
                beanDefinition.getPropertyValues().addPropertyValue(propertyValue);
            }
            if (getRegistry().containsBeanDefinition(beanName)) {
                throw new BeansException("Duplicate beanName[" + beanName + "] is not allowed");
            }
            // 注册 BeanDefinition
            getRegistry().registerBeanDefinition(beanName, beanDefinition);
        }
    }
}
  • XMLBeanDefinitionReader 类最核心的内容就是对 XML 文件的解析,把我们本来在代码中的操作放到了通过解析 XML 自动注册的方式。
    • loadBeanDefinitions 方法,处理资源加载,这里新增加了一个内部方法:doLoadBeanDefinitions,它主要负责解析 XML
    • doLoadBeanDefinitions 方法中,主要是对 XML 的读取:XmlUtil.readXML(inputStream) 和元素 Element 解析。
      • 在解析的过程中通过循环操作,以此获取 Bean 配置以及配置中的 id、name、class、value、ref 信息。
    • 最终把读取出来的配置信息,创建成 BeanDefinition 以及 PropertyValue ,最终把完整的 Bean 定义内容注册到 Bean 容器:
      • getRegistry().registerBeanDefinition(beanName, beanDefinition)

3.7 Bean工厂接口完善

3.7.1 Bean工厂接口

BeanFactory.java

java 复制代码
package com.lino.springframework.beans.factory;

import com.lino.springframework.beans.BeansException;

/**
 * @description: 定义 Bean 工厂接口
 */
public interface BeanFactory {

    /**
     * 返回 Bean 的实例对象
     *
     * @param name 要检索的bean的名称
     * @return 实例化的 Bean 对象
     * @throws BeansException 不能获取 Bean 对象,抛出异常
     */
    Object getBean(String name) throws BeansException;

    /**
     * 返回含构造函数的 Bean 实例对象
     *
     * @param name 要检索的bean的名称
     * @param args 构造函数入参
     * @return 实例化的 Bean 对象
     * @throws BeansException 不能获取 Bean 对象,抛出异常
     */
    Object getBean(String name, Object... args) throws BeansException;

    /**
     * 返回指定泛型的对象
     *
     * @param name         要检索的bean的名称
     * @param requiredType 类型
     * @param <T>          泛型
     * @return 实例化的的 Bean 对象
     * @throws BeansException 不能获取 Bean 对象,抛出异常
     */
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;
}

3.7.2 扩展Bean工厂子接口

ListableBeanFactory.java

java 复制代码
package com.lino.springframework.beans.factory;

import com.lino.springframework.beans.BeansException;

import java.util.Map;

/**
 * @description: Listable Bean 工厂子接口
 */
public interface ListableBeanFactory extends BeanFactory {
    /**
     * 按照类型返回 Bean 实例
     *
     * @param type 类型
     * @param <T>  泛型
     * @return 泛型Map
     * @throws BeansException Bean异常
     */
    <T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException;

    /**
     * 返回注册表中所有的Bean名称
     *
     * @return 注册表中所有的Bean名称
     */
    String[] getBeanDefinitionNames();
}

3.7.3 扩展Bean工厂的层次子接口

HierarchicalBeanFactory.java

java 复制代码
package com.lino.springframework.beans.factory;

/**
 * @description: hierarchy bean工厂层次子接口
 */
public interface HierarchicalBeanFactory extends BeanFactory {
}

3.7.4 自动化处理Bean工厂配置接口

AutowireCapableBeanFactory.java

java 复制代码
package com.lino.springframework.beans.factory.config;

import com.lino.springframework.beans.factory.BeanFactory;

/**
 * @description: 自动化处理Bean工厂配置接口
 */
public interface AutowireCapableBeanFactory extends BeanFactory {
}

3.7.5 Bean工厂配置化接口

ConfigurableBeanFactory.java

java 复制代码
package com.lino.springframework.beans.factory.config;

import com.lino.springframework.beans.factory.HierarchicalBeanFactory;

/**
 * @description: 配置Bean工厂接口
 */
public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry {

    String SCOPE_SINGLETON = "singleton";

    String SCOPE_PROTOTYPE = "prototype";
}

3.7.6 Bean工厂预先实例化的操作接口

ConfigurableListableBeanFactory.java

java 复制代码
package com.lino.springframework.beans.factory;

import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.factory.config.AutowireCapableBeanFactory;
import com.lino.springframework.beans.factory.config.BeanDefinition;
import com.lino.springframework.beans.factory.config.ConfigurableBeanFactory;

/**
 * @description: 配置列表 Bean工厂接口
 */
public interface ConfigurableListableBeanFactory extends ListableBeanFactory, AutowireCapableBeanFactory, ConfigurableBeanFactory {

    /**
     * 根据对象名称获取bean对象
     *
     * @param beanName 对象名称
     * @return bean对象
     * @throws BeansException bean异常
     */
    BeanDefinition getBeanDefinition(String beanName) throws BeansException;
}

3.8 Bean接口及其实现类完善

3.8.1 Bean工厂抽象类

AbstractBeanFactory.java

java 复制代码
package com.lino.springframework.beans.factory.support;

import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.factory.BeanFactory;
import com.lino.springframework.beans.factory.config.BeanDefinition;

/**
 * @description: 抽象的 Bean 工厂基类,定义模板方法
 */
public abstract class AbstractBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory {

    @Override
    public Object getBean(String name) throws BeansException {
        return doGetBean(name, null);
    }

    @Override
    public Object getBean(String name, Object... args) throws BeansException {
        return doGetBean(name, args);
    }

    @Override
    public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
        return (T) getBean(name);
    }

    protected <T> T doGetBean(final String name, final Object[] args) {
        Object bean = getSingleton(name);
        if (bean != null) {
            return (T) bean;
        }
        BeanDefinition beanDefinition = getBeanDefinition(name);
        return (T) createBean(name, beanDefinition, args);
    }

    /**
     * 获取 Bean 对象
     *
     * @param beanName 要检索的bean的名称
     * @return Bean 对象
     */
    protected abstract BeanDefinition getBeanDefinition(String beanName);

    /**
     * 创建Bean对象
     *
     * @param beanName       要检索的bean的名称
     * @param beanDefinition Bean对象
     * @param args           构造函数入参
     * @return 实例化的Bean对象
     */
    protected abstract Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args);
}

3.8.2 修改Bean注册接口

BeanDefinitionRegistry.java

java 复制代码
package com.lino.springframework.beans.factory.support;

import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.factory.config.BeanDefinition;

/**
 * @description: Bean 定义注册接口
 */
public interface BeanDefinitionRegistry {

    /**
     * 向注册表中注册 BeanDefinition
     *
     * @param beanName       Bean 名称
     * @param beanDefinition Bean 定义
     */
    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);

    /**
     * 使用Bean名称查询BeanDefinition
     *
     * @param beanName bean名称
     * @return bean对象
     * @throws BeansException bean异常
     */
    BeanDefinition getBeanDefinition(String beanName) throws BeansException;

    /**
     * 判断是否包含指定名称的BeanDefinition
     *
     * @param beanName bean名称
     * @return 是否包含
     */
    boolean containsBeanDefinition(String beanName);

    /**
     * 返回注册表中所有的Bean对象
     *
     * @return Bean对象数组
     */
    String[] getBeanDefinitionNames();
}

3.8.3 默认的Bean工厂实现类

DefaultListableBeanFactory.java

java 复制代码
package com.lino.springframework.beans.factory.support;

import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.factory.ConfigurableListableBeanFactory;
import com.lino.springframework.beans.factory.config.BeanDefinition;

import java.util.HashMap;
import java.util.Map;

/**
 * @description: 默认的Bean工厂实现类
 */
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements BeanDefinitionRegistry, ConfigurableListableBeanFactory {

    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();

    @Override
    public BeanDefinition getBeanDefinition(String beanName) {
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if (beanDefinition == null) {
            throw new BeansException("No bean named '" + beanName + "' is defined");
        }
        return beanDefinition;
    }

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
        beanDefinitionMap.put(beanName, beanDefinition);
    }

    @Override
    public boolean containsBeanDefinition(String beanName) {
        return beanDefinitionMap.containsKey(beanName);
    }

    @Override
    public <T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException {
        Map<String, T> result = new HashMap<>(16);
        beanDefinitionMap.forEach((beanName, beanDefinition) -> {
            Class beanClass = beanDefinition.getBeanClass();
            if (type.isAssignableFrom(beanClass)) {
                result.put(beanName, (T) getBean(beanName));
            }
        });
        return result;
    }

    @Override
    public String[] getBeanDefinitionNames() {
        return beanDefinitionMap.keySet().toArray(new String[0]);
    }
}

四、测试:资源加载器解析文件注册对象

4.1 添加配置文件

💡 这里的两份配置文件,一份用于测试资源加载器,另外 spring.xml 用于测试整体的 Bean 注册功能。

4.1.1 properties配置文件

important.properties

yaml 复制代码
# Config File
system.key=OLpj9823dZ

4.1.2 xml配置文件

spring.xml

xml 复制代码
<?xml version="1.0" encoding="utf-8" ?>
<beans>
    <bean id="userDao" class="com.lino.springframework.test.bean.UserDao"/>

    <bean id="userService" class="com.lino.springframework.test.bean.UserService">
        <property name="uId" value="10001"/>
        <property name="userDao" ref="userDao"/>
    </bean>
</beans>

4.2 单元测试

4.2.1 xml配置测试

ApiTest.java

java 复制代码
package com.lino.springframework.test;

import cn.hutool.core.io.IoUtil;
import com.lino.springframework.beans.factory.support.DefaultListableBeanFactory;
import com.lino.springframework.beans.factory.xml.XMLBeanDefinitionReader;
import com.lino.springframework.core.io.DefaultResourceLoader;
import com.lino.springframework.core.io.Resource;
import com.lino.springframework.test.bean.UserService;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;

/**
 * @description: 测试类
 */
public class ApiTest {

    private DefaultResourceLoader resourceLoader;

    @Before
    public void init() {
        resourceLoader = new DefaultResourceLoader();
    }

    @Test
    public void test_xml() {
        // 1.初始化 BeanFactory
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 2.读取配置文件&注册Bean
        XMLBeanDefinitionReader reader = new XMLBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions("classpath:spring.xml");

        // 3.获取Bean对象调用方法
        UserService userService = beanFactory.getBean("userService", UserService.class);
        String result = userService.queryUserInfo();
        System.out.println("测试结果:" + result);
    }
}

测试结果

java 复制代码
查询用户信息: 张三
  • 从测试结果来看,已经把注册 Bean 以及配置属性信息的内容,交给了 newXMLBeanDefinitionReader(beanFactory) 类读取 spring.xml 的方式来处理,并通过了测试。

4.2.2 加载类路径测试

ApiTest.java

java 复制代码
@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);
}

测试结果

yaml 复制代码
# Config File
system.key=OLpj9823dZ

4.2.3 加载文件路径测试

ApiTest.java

java 复制代码
@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);
}

测试结果

yaml 复制代码
# Config File
system.key=OLpj9823dZ

4.2.4 加载URL路径测试

ApiTest.java

java 复制代码
@Test
public void test_url() throws IOException {
    Resource resource = resourceLoader.getResource("https://github.com/fuzhengwei/small-spring/important.properties");
    InputStream inputStream = resource.getInputStream();
    String content = IoUtil.readUtf8(inputStream);
    System.out.println(content);
}

测试结果

yaml 复制代码
# Config File
system.key=OLpj9823dZ

五、总结:资源加载器解析文件注册对象

  • 此时的工程结构,以配置文件为入口解析和注册 Bean 信息,最终再通过 Bean 工厂获取 Bean 以及做相应的调用操作。
相关推荐
小bo波40 分钟前
枚举实战
java·设计模式·枚举·后端开发·代码重构
夜微凉41 小时前
三、Spring
java·后端·spring
橘右今1 小时前
2026 Java后端高频面试宝典
java·开发语言·面试
xyzzklk2 小时前
解决Salesforce无法向外发送邮件
android·java·开发语言·网络·crm·salesforce·客户关系管理
biubiubiu07062 小时前
SpringBoot关于外部化配置
java·spring boot·spring
Full Stack Developme3 小时前
Spring Bean 依赖注入
python·spring·log4j
zzz_23683 小时前
【Spring】面试突击系列(二):SpringBoot 入门与自动配置原理
java·spring boot·spring
Full Stack Developme3 小时前
Spring AOP 与 AspectJ
java·后端·spring
快乐的木子李3 小时前
最新版Maven免安装配置教程
java·maven
wuminyu4 小时前
Java锁机制之Java对象重量级锁源码剖析
java·linux·c语言·jvm·c++