第二章 Spring之假如让你来写IOC容器——加载资源篇

文章通过模拟实现Spring的IOC容器,逐步介绍了从加载资源配置、解析XML到创建Bean的过程,涉及BeanFactory、FactoryBean、属性填充和不同资源加载策略等关键概念。文章以代码实践的方式,帮助读者理解Spring的核心机制。
摘要由CSDN通过智能技术生成

Spring源码阅读目录

第一章 Spring之最熟悉的陌生人——IOC
第二章 Spring之假如让你来写IOC容器——加载资源篇
第三章 Spring之假如让你来写IOC容器——解析配置文件篇
第四章 Spring之假如让你来写IOC容器——XML配置文件篇
第五章 Spring之假如让你来写IOC容器——BeanFactory和FactoryBean
第六章 Spring之假如让你来写IOC容器——Scope和属性填充
第七章 Spring之假如让你来写IOC容器——属性填充特别篇:SpEL表达式
第八章 Spring之假如让你来写IOC容器——拓展篇
第九章 Spring之源码阅读——环境搭建篇
第十章 Spring之源码阅读——IOC篇



前言

    对于Spring一直都是既熟悉又陌生,说对它熟悉吧,平时用用没啥问题,但面试的时候被问的一脸懵逼,就很尴尬,都不好意思在简历上写着熟悉Spring了
在这里插入图片描述

    所以决定花点时间研究研究Spring的源码。主要参考的书籍是:《Spring源码深度解析(第2版)》、《Spring揭秘》、《Spring技术内幕:深入解析Spring架构与设计原理(第2版)》


一些废话

    一说到IOC,相信大家脑子里都会浮现出很多面试题,诸如:Spring如何解决循环依赖、BeanFactory和FactoryBean的区别、Spring的生命周期等等。不过这里先不贴代码直接看了,之前我也试过debug一步步跟,几分钟过后:我是谁?我在那里?我在干什么?

    不妨换个思路,如果让我们自己写个IOC容器,我们会这么写?会从哪些方面考虑?完整项目
在这里插入图片描述

尝试动手写IOC容器

    出场人物:A君(苦逼的开发)、老大(项目经理)

    背景:老大要求A君在一周内开发个简单的IOC容器

    现在老大突然要让A君自己来开发一个IOC容器A君接到任务后,不禁心里暗骂:不是已经有现成的IOC容器了吗?为啥还要自己写@#¥%

    骂归骂,活还是要干的,A君先用idea新建个maven项目,这个就不细说了,在引入一下依赖,pom文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.hqd</groupId>
    <artifactId>spring-imitation</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
        </dependency>
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.0.0</version>
        </dependency>
        <!-- BeanUtils的依赖 -->
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>

        <!-- junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
</project>

    定义一个简单的User对象用来测试,如下:

import lombok.Data;

@Data
public class User {
    private String name;
    private int age;
}

    对于Bean的描述,只需要最简单的几个属性就行了,定义一个简单的bean.xml,如下:

<beans>
    <bean id="user" class="com.hqd.ch03.bean.User">
        <property name="name" value="zs"/>
        <property name="age" value="14"/>
    </bean>
</beans>

第一版 面向过程实现

    由于是上头要求的,任务重,工期紧,A君不管三七二十一,先按面向过程的思路来做,要实现一个IOC容器,大致可以分为以下几步:

  1. 加载配置文件
  2. 解析配置文件
  3. 根据配置生成对象
  4. 将对象放在map里

分析完步骤,A君直接撸袖子干,代码如下:



import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections.CollectionUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

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

/**
 *
 */
public class SpringImitationV1 {
    /**
     * 对象缓存
     */
    private Map<String, Object> bdMap = new HashMap<>();

    public SpringImitationV1(String path) {
        this.init(path);
    }

    public <T> T getBean(String name, Class<T> clazz) {
        return (T) bdMap.get(name);
    }

    /**
     * 初始化bean
     *
     * @param bean
     * @param properties
     */
    private void initBean(Object bean, List<Element> properties) {
        try {
            properties.forEach(property -> {
                String name = property.attributeValue("name");
                String value = property.attributeValue("value");
                try {
                    BeanUtils.setProperty(bean, name, value);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 创建bean对象
     *
     * @param beanEles
     */
    private void createBeans(List<Element> beanEles) {
        beanEles.forEach(beanEle -> {
            String className = beanEle.attributeValue("class");
            Object bean = createBean(className);
            List<Element> properties = beanEle.elements("property");
            if (CollectionUtils.isNotEmpty(properties)) {
                initBean(bean, properties);
            }
            bdMap.put(beanEle.attributeValue("id"), bean);
        });
    }

    private Object createBean(String className) {
        try {
            return Class.forName(className).getConstructor().newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 加载配置
     *
     * @param path
     * @return
     */
    private Document loadConfig(String path) {
        SAXReader reader = new SAXReader();
        try {
            return reader.read(SpringImitationV1.class.getClassLoader().getResource(path));
        } catch (DocumentException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 解析配置文件
     *
     * @param document
     * @return
     */
    private List<Element> parseConfig(Document document) {
        Element rootElement = document.getRootElement();
        return rootElement.elements("bean");
    }

    private void init(String path) {
        Document document = loadConfig(path);
        List<Element> beanEles = parseConfig(document);
        createBeans(beanEles);
    }
}

A君三下五除二,几下就写完了,接下来做个简单的测试,代码如下:

	 @Test
    public void v1() {
        System.out.println("############# 第一版 #############");
        SpringImitationV1 bean = new SpringImitationV1("v1/bean.xml");
        User user = bean.getBean("user", User.class);
        System.out.println(user);
    }

输入如下:

在这里插入图片描述

    测试完之后,A君准备收工跑路,就把代码提交上去给老大过目

    老大看了下,说:嗯,A君,还不错。但是你这个加载配置文件只能在类路径下,如果有些用户想把配置文件放在项目外,方便修改。这时候要怎么办呢?

第二版 统一资源加载

    第一版被老大打回来之后,A君只能再修改一版,不过这时候A君留了个心眼:现在用户想要把配置放在项目外边,回头可能就放在类路径下甚至其他地方,不可能天天陪着用户改来改去

    想到了这里,A君就设计一个Resource接口,这个接口只负责获取流对象,如下:


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

/**
 * 配置文件存放路径的接口定义
 */
public interface Resource {
    /**
     * 获取流对象
     *
     * @return
     */
    InputStream getInputStream() throws IOException;
}

    有了接口之后,就可以提供不同的实现方式来应对用户不同的需求了,这也是多态有魅力的地方,首先是基于类路径的实现,如下:

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

/**
 * 类路径下的资源
 */
public class ClassPathResource implements Resource {
    private String path;

    public ClassPathResource(String path) {
        this.path = path;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return this.getClass().getClassLoader().getResourceAsStream(path);
    }
}

接下来是基于文件系统的实现,这个就更简单了,如下:


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

/**
 * 文件系统路径下的资源
 */
public class FileSystemResource implements Resource {
    private String path;

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

    @Override
    public InputStream getInputStream() throws IOException {
        return new FileInputStream(path);
    }
}

做完以上操作后,用户只要传入个Resource接口的实现就可以了,再来修改下SpringImitation类,改造如下:

在这里插入图片描述

init方法,如下:

在这里插入图片描述

loadConfig方法,如下:

在这里插入图片描述

完整代码:


import com.hqd.ch03.v2.io.Resource;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections.CollectionUtils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class SpringImitationV2 {
    /**
     * 对象缓存
     */
    private Map<String, Object> bdMap = new HashMap<>();

    public SpringImitationV2(Resource resource) {
        this.init(resource);
    }

    public <T> T getBean(String name, Class<T> clazz) {
        return (T) bdMap.get(name);
    }

    /**
     * 初始化bean
     *
     * @param bean
     * @param properties
     */
    private void initBean(Object bean, List<Element> properties) {
        try {
            properties.forEach(property -> {
                String name = property.attributeValue("name");
                String value = property.attributeValue("value");
                try {
                    BeanUtils.setProperty(bean, name, value);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 创建bean对象
     *
     * @param beanEles
     */
    private void createBeans(List<Element> beanEles) {
        beanEles.forEach(beanEle -> {
            String className = beanEle.attributeValue("class");
            Object bean = createBean(className);
            List<Element> properties = beanEle.elements("property");
            if (CollectionUtils.isNotEmpty(properties)) {
                initBean(bean, properties);
            }
            bdMap.put(beanEle.attributeValue("id"), bean);
        });
    }

    private Object createBean(String className) {
        try {
            return Class.forName(className).getConstructor().newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 加载配置
     *
     * @param resource
     * @return
     */
    private Document loadConfig(Resource resource) {
        SAXReader reader = new SAXReader();
        try {
            return reader.read(resource.getInputStream());
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                resource.getInputStream().close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 解析配置文件
     *
     * @param document
     * @return
     */
    private List<Element> parseConfig(Document document) {
        Element rootElement = document.getRootElement();
        return rootElement.elements("bean");
    }

    private void init(Resource resource) {
        Document document = loadConfig(resource);
        List<Element> beanEles = parseConfig(document);
        createBeans(beanEles);
    }
}

再来编写下测试代码,如下:


	 /**
     * 抽象资源文件
     */
    @Test
    public void v2() {
        System.out.println("############# 第二版:抽象资源 #############");
        SpringImitationV2 bean = new SpringImitationV2(new ClassPathResource("v2/bean.xml"));
        User user = bean.getBean("user", User.class);
        System.out.println("基于类路径加载:" + user);
        SpringImitationV2 bean1 = new SpringImitationV2(new FileSystemResource("E:/代码/java/JavaEE/spring-imitation/src/main/resources/v2/bean.xml"));
        User user1 = bean1.getBean("user", User.class);
        System.out.println("基于文件系统路径加载:" + user1);
    }

输入如下:

在这里插入图片描述

    有了Resource接口之后,变化是显而易见,可以提供不同的Resource实现来获取各种形式存在的配置文件了。想到这里,A君兴高采烈的把代码提交给老大

    老大再次看了下,又说:A君啊,这个用户还需要知道你Resource接口的具体实现,如果再简单一点就好了,毕竟提供给别人用的东西,越简单就越好,最好是开箱即用的那种

第三版 简化资源接口使用

    老大既然发话了,为了方便用户使用,继续改呗

    A君经过一番研究,发现URL可以描述很多协议:file://、http:// 等。可以参考URL协议,定义以下协议:classpath:,file:,分别用来描述不同的读取方式

    有了想法之后,说干就干,老样子,A君还是先定义一个ResourceLoader接口,用来加载定义的协议。接口只有一个方法,就是通过传入的协议串来获取不同的资源,接口如下:


/**
 * 配置文件加载接口
 */
public interface ResourceLoader {
    String CLASSPATH_URL_PREFIX = "classpath:";
    String FILE_URL_PREFIX = "file:";

    /**
     * 获取资源
     *
     * @param location
     * @return
     */
    Resource getResource(String location);
}

有了接口之后,需要在提供一个默认的实现,实现也很简单,根据字符串的开头进行判断,返回具体的资源即可,代码如下:


/**
 * 默认资源加载器
 */
public class DefaultResourceLoader implements ResourceLoader {

    @Override
    public Resource getResource(String location) {
        if (location.startsWith(CLASSPATH_URL_PREFIX)) {
            return new ClassPathResource(location.replace(CLASSPATH_URL_PREFIX, ""));
        } else if (location.startsWith(FILE_URL_PREFIX)) {
            return new FileSystemResource(location.replace(FILE_URL_PREFIX, ""));
        }
        return null;
    }
}

做好上边这些事之后,还需要修改下SpringImitation类,SpringImitation类需要提供个ResourceLoader的实现,用来解析用户传入的字符串,改造如下:

在这里插入图片描述

init方法如下:

在这里插入图片描述

loadConfig方法如下:

在这里插入图片描述

完整代码:


import com.hqd.ch03.v3.io.DefaultResourceLoader;
import com.hqd.ch03.v3.io.Resource;
import com.hqd.ch03.v3.io.ResourceLoader;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections.CollectionUtils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class SpringImitationV3 {
    /**
     * 对象缓存
     */
    private Map<String, Object> bdMap = new HashMap<>();
    /**
     * 资源加载器
     */
    private ResourceLoader resourceLoader = new DefaultResourceLoader();

    public SpringImitationV3(String path) {
        this.init(path);
    }

    /**
     * 初始化bean
     *
     * @param bean
     * @param properties
     */
    private void initBean(Object bean, List<Element> properties) {
        try {
            properties.forEach(property -> {
                String name = property.attributeValue("name");
                String value = property.attributeValue("value");
                try {
                    BeanUtils.setProperty(bean, name, value);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 创建bean对象
     *
     * @param beanEles
     */
    private void createBeans(List<Element> beanEles) {
        beanEles.forEach(beanEle -> {
            String className = beanEle.attributeValue("class");
            Object bean = createBean(className);
            List<Element> properties = beanEle.elements("property");
            if (CollectionUtils.isNotEmpty(properties)) {
                initBean(bean, properties);
            }
            bdMap.put(beanEle.attributeValue("id"), bean);
        });
    }

    private Object createBean(String className) {
        try {
            return Class.forName(className).getConstructor().newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 加载配置
     *
     * @param path
     * @return
     */
    private Document loadConfig(String path) {

        Resource resource = resourceLoader.getResource(path);
        try {
            SAXReader reader = new SAXReader();
            return reader.read(resource.getInputStream());
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                resource.getInputStream().close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public <T> T getBean(String name, Class<T> clazz) {
        return (T) bdMap.get(name);
    }

    /**
     * 解析配置文件
     *
     * @param document
     * @return
     */
    private List<Element> parseConfig(Document document) {
        Element rootElement = document.getRootElement();
        return rootElement.elements("bean");
    }


    private void init(String path) {
        Document document = loadConfig(path);
        List<Element> beanEles = parseConfig(document);
        createBeans(beanEles);
    }
}

修改完SpringImitation类,接下来在做个简单的测试,如下:

 /**
     * 添加资源加载器
     */
    @Test
    public void v3() {
        System.out.println("############# 第三版:简化资源接口使用 #############");
        SpringImitationV3 bean = new SpringImitationV3("classpath:v3/bean.xml");
        User user = bean.getBean("user", User.class);
        System.out.println("基于类路径加载:" + user);
        SpringImitationV3 bean1 = new SpringImitationV3("file:E:/代码/java/JavaEE/spring-imitation/src/main/resources/v3/bean.xml");
        User user1 = bean1.getBean("user", User.class);
        System.out.println("基于文件系统路径加载:" + user1);
    }

输出如下:

在这里插入图片描述

    问题不大,今天又可以准时下班了,接着A君美滋滋的的把新版的代码提交给了老大,这次总没问题了吧?

    老大看了看代码,瞟A君了一眼,说:寻找资源是没啥问题了,但是配置文件有问题,配置文件可是有多种多样的,目前这个只支持XML,如果是Properties、YML、JSON的呢?


总结

    正所谓树欲静而风不止,欲知后事如何,请看下回分解(✪ω✪)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

穷儒公羊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值