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

Spring源码阅读目录

第一部分——IOC篇

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

第二部分——AOP篇

第十一章 Spring之不太熟的熟人——AOP
第十二章 Spring之不得不了解的内容——概念篇
第十三章 Spring之假如让你来写AOP——AOP联盟篇
第十四章 Spring之假如让你来写AOP——雏形篇
第十五章 Spring之假如让你来写AOP——Joinpoint(连接点)篇
第十六章 Spring之假如让你来写AOP——Pointcut(切点)篇
第十七章 Spring之假如让你来写AOP——Advice(通知)上篇
第十八章 Spring之假如让你来写AOP——Advice(通知)下篇
第十九章 Spring之假如让你来写AOP——番外篇:Spring早期设计
第二十章 Spring之假如让你来写AOP——Aspect(切面)篇
第二十一章 Spring之假如让你来写AOP——Weaver(织入器)篇
第二十二章 Spring之假如让你来写AOP——Target Object(目标对象)篇
第二十三章 Spring之假如让你来写AOP——融入IOC容器篇
第二十四章 Spring之源码阅读——AOP篇

第三部分——事务篇

第二十五章 Spring之曾经的老朋友——事务
第二十六章 Spring之假如让你来写事务——初稿篇
第二十七章 Spring之假如让你来写事务——铁三角篇
第二十八章 Spring之假如让你来写事务——属性篇
第二十九章 Spring之假如让你来写事务——状态篇
第三十章 Spring之假如让你来写事务——管理篇
第三十一章 Spring之假如让你来写事务——融入IOC容器篇
第三十二章 Spring之源码阅读——事务篇

第四部分——MVC篇

第三十三章 Spring之梦开始的地方——MVC
第三十四章 Spring之假如让你来写MVC——草图篇
第三十五章 Spring之假如让你来写MVC——映射器篇
第三十六章 Spring之假如让你来写MVC——拦截器篇
第三十七章 Spring之假如让你来写MVC——控制器篇
第三十八章 Spring之假如让你来写MVC——适配器篇
第三十九章 Spring之假如让你来写MVC——番外篇:类型转换
第四十章 Spring之假如让你来写MVC——ModelAndView篇
第四十一章 Spring之假如让你来写MVC——番外篇:数据绑定
第四十二章 Spring之假如让你来写MVC——视图篇
第四十三章 Spring之假如让你来写MVC——上传文件篇
第四十四章 Spring之假如让你来写MVC——异常处理器篇
第四十五章 Spring之假如让你来写MVC——国际化篇
第四十六章 Spring之假如让你来写MVC——主题解析器篇
第四十七章 Spring之假如让你来写MVC——闪存管理器篇
第四十八章 Spring之假如让你来写MVC——请求映射视图篇
第四十九章 Spring之假如让你来写MVC——番外篇:属性操作
第五十章 Spring之假如让你来写MVC——融入IOC容器篇
第五十一章 Spring之源码阅读——MVC篇

第五部分——Boot篇

第五十二章 Spring之再进一步——Boot
第五十三章 Spring之假如让你来写Boot——环境篇
第五十四章 Spring之假如让你来写Boot——注解篇(上)
第五十五章 Spring之假如让你来写Boot——注解篇(下)
第五十六章 Spring之假如让你来写Boot——SPI篇
第五十七章 Spring之假如让你来写Boot——配置文件篇(上)
第五十八章 Spring之假如让你来写Boot——配置文件篇(下)
第五十九章 Spring之假如让你来写Boot——番外篇:再谈Bean定义
第六十章 Spring之假如让你来写Boot——自动装配篇
第六十一章 Spring之假如让你来写Boot——番外篇:杂谈Starter
第六十二章 Spring之假如让你来写Boot——番外篇:重构BeanFactory
第六十三章 Spring之假如让你来写Boot——番外篇:再谈ApplicationContext
第六十四章 Spring之假如让你来写Boot——内嵌Web容器篇
第六十五章 Spring之假如让你来写Boot——Main方法启动篇
第六十六章 Spring之最终章——结语篇



前言

    对于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的呢?


总结

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

穷儒公羊

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

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

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

打赏作者

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

抵扣说明:

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

余额充值