手写简单的Spring框架——my-mini-spring -- IOC篇 -- 简单的IOC容器 (终)

项目的Github地址:github仓库
Gitee地址:gitee仓库
个人博客地址sillybaka的博客

此篇所在的分支为:resource-and-loaderxml-define-bean
该篇 1、实现了将所有资源抽象成一个Resource接口,补充了URL的不足之处

2、实现了资源加载器ResourceLoader,支持根据类路径和文件系统路径等方式获取Resource

3、实现了读取XML文件中的Bean配置

简单的IOC容器

5、资源的抽象及获取(读取XML配置文件)

5.1、Resource接口(Spring中所有资源的抽象)

**前言:**在Spring中,通常是通过XML文件来配置Bean、定义Bean的信息,同时也用来引入第三方框架的配置。那么Spring要如何为XML配置文件的加载提供支持呢?

Java的标准java.net.URL类和各种URL前缀的标准处理程序无法满足所有对low-level资源的访问.

比如:没有标准化的URL实现类用于获取根据ServletContext的类路径。并且缺少某些Spring所需要的功能,例如检测某资源是否存在等,java.net.url只提供了基于标准URL来访问资源的方法**(实际上只针对网络上发布的web资源),而不能基于特定的路径来访问特定的资源(无法针对文件资源、类路径资源)**

Java SE对于资源定位提供了URL这一类,即Uniform Resource Locator,虽然号称统一资源定位,实际上只对通过网络发布的资源提供查找和定位功能;

实际上,资源这个词的范围比较广义,资源可以任何形式存在,如以二进制对象形式存在、以字节流形式存在、以文件形式存在等;而且,资源也可以存在于任何场所,如存在于文件系统、存在于Java应用的Classpath中,甚至存在于URL可以定位的地方。

其次,该类的功能职责划分不清,资源的查找和表示没有清晰的界限;当前情况是,资源查找后返回的形式多种多样,没有一个统一的抽象。理想情况下,资源查找完成后,返回给客户端的应该是一个统一的资源抽象接口,客户端要对资源进行什么样的处理,应该由资源抽象接口来界定,而不应该成为资源的定位者和查找者同时要关心的事情

Resource接口Spring中所有资源的抽象和访问接口,用于解决URL接口的不足

https://raw.githubusercontent.com/Silly-Baka/my-pics/main/img/webp.webp?token=ATHSE325TZSKGAKIZFQFW7TDJO7O2 Spring中Resource接口的UML关系图

resource-16658411461912.png

使用策略模式,在获取资源时先获取当前的上下文,再根据上下文的类型来获取相应类型的Resource类对象

public interface Resource {

    /**
     * 判断当前资源是否存在
     */
    boolean isExist();

    /**
     * 获取当前资源的二进制流 由具体Resource类实现
     */
    InputStream getInputStream() throws FileNotFoundException;

    /**
     * 获取当前资源的URL
     */
    URL getURL() throws FileNotFoundException;

    /**
     * 获取当前资源的URI
     */
    URI getURI();

    /**
     * 获取该资源文件的名字
     */
    String getFileName();
}
1、 FileSystemResource(文件系统资源)

FileSystemResource指的是基于文件系统获取的资源,一般是服务器本地的文件资源

2、 ClassPathResource(类路径资源)

ClassPathResource表示从类路径获取的资源,通常使用线程上下文的ClassLoader进行资源加载.
我们的Web项目通常编译后,会将class文件存储在WEB-INF/classes下,Spring就可以通过ClassPathResource来访问这些文件.

3、 UrlResource(Url资源)

UrlResource表示基于URL路径获取的资源,一般是一种web资源。

5.2 ResourceLoader接口(Spring中定位资源策略的抽象)

image-20221108101010326

Spring中ResourceLoader接口的UML关系图

https://raw.githubusercontent.com/Silly-Baka/my-pics/main/img/image-20221016120015378-166589281704010.png?token=ATHSE3YBY3CS27JWJTM2PATDJO7PQ

资源是有了,但如何去查找和定位这些资源,则就是ResourceLoader的职责所在了。

ResourceLoader接口是资源查找定位策略的统一抽象,具体的资源查找定位策略则由相应的ResourceLoader实现类给出

public interface ResourceLoader {
    /**
     * 根据资源路径加载相应的Resource
     * @param location 资源路径
     * @return
     */
    Resource getResource(String location);
}
1、DefaultResourceLoader

DefaultResourceLoader是**资源加载策略模式的实现**,可根据路径的前缀返回不同类型的Resource

代码实现

public class DefaultResourceLoader implements ResourceLoader{

    private static final String CLASSPATH_PREFIX = "classpath:";

    @Override
    public Resource getResource(String location) {
        // 如果以classpath开头,则返回ClassPathResource
        if(location.startsWith(CLASSPATH_PREFIX)){
            return new ClassPathResource(location.substring(CLASSPATH_PREFIX.length()));
        }else {
        // 否则先尝试转换成url
            try {
                URL url = new URL(location);
                return new UrlResource(url);
            } catch (MalformedURLException e) {
            // url格式错误,再尝试使用file体系
                return new FileSystemResource(location);
            }
        }
    }
}

6、更新BeanFactory的继承关系

更新后的继承关系

更新前的继承关系

更新前的继承关系

6.1 HierarchicalBeanFactory(分层的BeanFactory)

HierarchicalBeanFactory为BeanFactory提供了分层的能力,让实现这个接口的BeanFactory之间拥有层级关系(子级BeanFactory无法获得父级BeanFactory定义的Bean),为BeanFactory提供了可以获取父级BeanFactory的接口

6.2 ListableBeanFactory(可列表化的BeanFactory)

实现了ListableBeanFactory接口的BeanFactory可以一次性列出所有需要的Bean的信息,提供了查询Bean的能力

6.3 ConfigurableBeanFactory(可配置的BeanFactory)

ConfigurableBeanFactory为BeanFactory提供了多个配置BeanFactory的接口,允许框架开发者对BeanFactory进行自定义配置

6.4 AutowireCapableBeanFactory(可自动装配的BeanFactory)

AutowireCapableBeanFactory为BeanFactory提供了自动装配Bean属性的接口

6.5 ConfigurableListableBeanFactory

ConfigurableListableBeanFactory接口**整合了BeanFactory所需的所有特性,同时还提供了分析和修改Bean的工具,也提供了解决循环依赖的方法(预定义Bean实例)**

7、 读取配置文件的Bean定义

有了**Resource和ResourceLoader,就可以读取配置文件**,现在可以开始实现使用配置文件定义Bean的功能

BeanDefinitionReader的继承关系

Spring中读取Bean定义的接口BeanDefinitionReader的继承关系

在本项目my-mini-spring中,只实现以XML文件的类型配置Bean的定义

所以继承关系如下:

image-20221016203946158-166619434120210

7.1 BeanDefinitionReader

流程:

  1. 通过配置文件的locations,获取配置文件的资源Resource
  2. 读取每一个资源**Resource **(需要ResourceLoader)
  3. 解析读取到的内容,转化为BeanDefinition
  4. 将BeanDefinition 注册到BeanDefinitionRegistry(需要BeanDefinitionRegistry)

BeanDefinitionReader接口定义

public interface BeanDefinitionReader {

    ResourceLoader getResourceLoader();

    BeanDefinitionRegistry getBeanDefinitionRegistry();

    void loadBeanDefinitions(String location);

    void loadBeanDefinitions(String[] locations);

    void loadBeanDefinitions(Resource resource);

    void loadBeanDefinitions(Resource[] resources);
}

7.2 XMLBeanDefinitionReader(实现读取XML文件中bean的逻辑)

XML示例

<bean id="xxx" class="xxx" >
	<property name="xxx" value="xxx"></property>
	// 第一种 直接注入(级联)
	<bean id="xxxx" class="xxxx">
	</bean>
	// 第二种 使用引用注入
	<property name="xxx" ref="refXXX"></property>
</bean>
<bean id="refXXX" class="xxx">
</bean>

代码实现

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader{

    public static final String BEAN_ELEMENT_TAG = "bean";
    public static final String PROPERTY_ELEMENT_TAG = "property";
    public static final String ID_ATTRIBUTE = "id";
    public static final String NAME_ATTRIBUTE = "name";
    public static final String CLASS_ATTRIBUTE = "class";
    public static final String VALUE_ATTRIBUTE = "value";
    public static final String REF_ATTRIBUTE = "ref";

    public XmlBeanDefinitionReader(ResourceLoader resourceLoader, BeanDefinitionRegistry beanDefinitionRegistry) {
        super(resourceLoader, beanDefinitionRegistry);
    }

    @Override
    public void loadBeanDefinitions(Resource resource) {
        try {
            try (InputStream inputStream = resource.getInputStream()) {
                doLoadBeanDefinitions(inputStream);
            }
        } catch (IOException e) {
            throw new BeansException("");
        }
    }

    /**
     * 读取XML配置文件中的BeanDefinitions
     * @param inputStream xml文件的二进制流
     */
    public void doLoadBeanDefinitions(InputStream inputStream){
        Document document = XmlUtil.readXML(inputStream);
        // 根标签
        Element root = document.getDocumentElement();
        // 根标签下的子节点
        NodeList childNodes = root.getChildNodes();
        int length = childNodes.getLength();
        for (int i = 0; i < length; i++) {
            // 如果子节点是一个标签 则开始解析
            if(childNodes.item(i) instanceof Element){
                Element element = (Element) childNodes.item(i);
                // 如果该标签为bean标签
                if(BEAN_ELEMENT_TAG.equals(element.getTagName())){
                    doLoadBeanDefinition(element);
                }
            }
        }
    }

    /**
     * 加载指定Bean标签的beanDefinition实际逻辑
     * @param beanElement bean标签
     */
    public BeanDefinition<?> doLoadBeanDefinition(Element beanElement){
        //解析bean标签
        String id = beanElement.getAttribute(ID_ATTRIBUTE);
        String name = beanElement.getAttribute(NAME_ATTRIBUTE);
        String className = beanElement.getAttribute(CLASS_ATTRIBUTE);

        Class clazz;
        try {
            clazz = Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw new BeansException("不存在该类型: [ "+ className + "] 的类",e);
        }

        // 存放Bean定义
        BeanDefinition<?> beanDefinition = new BeanDefinition<>();
        beanDefinition.setType(clazz);
        PropertyValues propertyValues = new PropertyValues();

        // 如果id不为空 则beanName为id
        String beanName = StrUtil.isNotBlank(id) ? id : name;

        // 若都为空 则beanName为类名(首字母小写)
        if(StrUtil.isBlank(beanName)){
            beanName = className.substring(className.lastIndexOf('.'));
            beanName = beanName.substring(0,1).toLowerCase(Locale.ROOT) + beanName.substring(1);
        }

        // 查询注册表中是否有同名的bean
        if(getBeanDefinitionRegistry().containsBeanDefinition(beanName)){
            throw new BeansException("不能注册重名的Bean,注册失败");
        }

        // 读取bean标签下的子标签(property,bean等)
        NodeList subNodes = beanElement.getChildNodes();
        int subLength = subNodes.getLength();
        for (int j = 0; j < subLength; j++) {
            if(subNodes.item(j) instanceof Element){
                Element subElement = (Element) subNodes.item(j);
                String subTagName = subElement.getTagName();
                // 如果是property标签
                if(PROPERTY_ELEMENT_TAG.equals(subTagName)){
                    String propertyName = subElement.getAttribute(NAME_ATTRIBUTE);
                    String propertyValue = subElement.getAttribute(VALUE_ATTRIBUTE);

                    PropertyValue pv;
                    // 是一个值
                    if(StrUtil.isNotBlank(propertyValue)){
                        //todo 这里的value是字符串类型 怎么转换为原类型?String可以转换成任意基本类型,而value只处理基本类型的属性
                        //  应该需要一个属性类型表 才方便转换
                        pv = new PropertyValue(propertyName, propertyValue);

                        PropertyUtils.addPropertyDescriptor(clazz,propertyName,null);

                    }else {
                        // 是一个引用 则propertyRef是引用的beanName
                        String propertyRef = subElement.getAttribute(REF_ATTRIBUTE);
                        BeanReference beanReference = new BeanReference(propertyRef);

                        // 引用作为属性值,自动装配时会注入bean实例
                        pv = new PropertyValue(propertyName,beanReference);

                        PropertyUtils.addPropertyDescriptor(clazz,propertyName,BeanReference.class);
                    }

                    // 添加属性到列表中
                    propertyValues.addPropertyValue(pv);

                }else if(BEAN_ELEMENT_TAG.equals(subTagName)){
                    //todo 如果是bean标签 则创建一个内置BeanDefinition 先不处理 嵌套太多 代码复杂

                    // 递归处理嵌套bean
                    BeanDefinition<?> subBeanDefinition = doLoadBeanDefinition(subElement);

                    String subBeanName = subElement.getAttribute(ID_ATTRIBUTE);

                    PropertyValue pv = new PropertyValue(subBeanName,subBeanDefinition);
                    PropertyUtils.addPropertyDescriptor(clazz,subBeanName,BeanDefinition.class);

                    propertyValues.addPropertyValue(pv);

                }
            }
        }
        beanDefinition.setPropertyValues(propertyValues);
        // 将bean定义注册进注册表中
        getBeanDefinitionRegistry().registerBeanDefinition(beanName,beanDefinition);

        return beanDefinition;
    }
}

测试

测试用的XML文件 test1.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="carBean1" class="sillybaka.springframework.entity.Car">
        <property name="brand" value="Benci"/>
        <property name="carRoll" ref="carRoll1"/>
    </bean>
    <bean id="carRoll1" class="sillybaka.springframework.entity.CarRoll">
        <property name="brand" value="niubiChelun"/>
    </bean>
</beans>

测试用的方法

@Test
public void testXmlReader(){
    ResourceLoader resourceLoader = new DefaultResourceLoader();
    BeanDefinitionRegistry beanDefinitionRegistry = new DefaultListableBeanFactory();
    BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(resourceLoader,beanDefinitionRegistry);

    // 1、先从xml配置文件中读取Bean定义 并注册进注册表
    beanDefinitionReader.loadBeanDefinitions("classpath:test1.xml");

    // 2、BeanFactory根据beanName获取bean实例(懒汉式创建)
    BeanFactory beanFactory = new DefaultListableBeanFactory();
    Object carBean1 = beanFactory.getBean("carBean1");

    System.out.println(carBean1);

    beanDefinitionReader.loadBeanDefinitions("classpath:test2.xml");

    Object carBean2 = beanFactory.getBean("carBean2");
    System.out.println(carBean2);

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值