第三章 Spring之假如让你来写IOC容器——解析配置文件篇

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版)》


    书接上回,在上篇 第二章 Spring源码阅读之假如让我来写IOC容器——加载资源篇 已经说到了如何通过协议加载不同路径下的资源,接下来继续看下如何解析配置文件吧

在这里插入图片描述

尝试动手写IOC容器

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

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

    前情提要:在A君提交代码后,老大提出配置文件可是有多种多样的,目前这个只支持XML,如果是Properties、YML、JSON的呢?

第四版 抽取配置文件接口

    A君骂骂咧咧的回到了工位:“XML不香吗?还要支持别的”,独自生了会儿闷气。A君又开始思考了,混口饭吃,不容易啊

    既然是想要多种实现,老样子,还是定一个接口BeanDefinitionReader,用来读取不同配置文件,接口代码如下:



import com.hqd.ch03.v4.io.Resource;

/**
 * 读取配置文件
 */
public interface BeanDefinitionReader {
    void loadBeanDefinition(Resource resource);
}

接口定义完成之后,就开始定义具体实现了。先是解析XML的配置文件,代码没什么变动,只不过换了个地方。代码如下:



import com.hqd.ch03.v4.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.util.List;
import java.util.Map;

/**
 * 解析xml配置
 */
public class XmlBeanDefinitionReader implements BeanDefinitionReader {
    private Map<String, Object> bdMap;

    public XmlBeanDefinitionReader(Map<String, Object> bdMap) {
        this.bdMap = bdMap;
    }

    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();
        }

    }

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

    @Override
    public void loadBeanDefinition(Resource resource) {
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(resource.getInputStream());
            Element rootElement = document.getRootElement();
            List<Element> beanEles = rootElement.elements("bean");
            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);
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

关于Properties的,这里偷个懒,先不实现了,如下:

import com.hqd.ch03.v4.io.Resource;

/**
 * 解析Properties配置
 */
public class PropertiesBeanDefinitionReader implements BeanDefinitionReader {
    @Override
    public void loadBeanDefinition(Resource resource) {
        //TODO
    }
}

由于配置文件有了多种实现,所以SpringImitation类也需要多种实现,先抽取公共实现为父类,如下:

import com.hqd.ch03.v4.io.DefaultResourceLoader;
import com.hqd.ch03.v4.io.ResourceLoader;

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

public abstract class SpringImitationV4 {
    protected Map<String, Object> bdMap = new HashMap<>();
    /**
     * 资源加载器
     */
    protected ResourceLoader resourceLoader = new DefaultResourceLoader();


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


    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}

提取完父类,接下来是XML配置的实现,如下:

import com.hqd.ch03.v4.reader.XmlBeanDefinitionReader;

public class SpringImitationV4Xml extends SpringImitationV4 {

    private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(bdMap);

    public SpringImitationV4Xml(String location) {
        this.reader.loadBeanDefinition(resourceLoader.getResource(location));
    }

}

再来个Properties的实现,如下:

import com.hqd.ch03.v4.reader.PropertiesBeanDefinitionReader;

public class SpringImitationV4Property extends SpringImitationV4 {
    private PropertiesBeanDefinitionReader reader = new PropertiesBeanDefinitionReader();

    public SpringImitationV4Property(String location) {
        this.reader.loadBeanDefinition(resourceLoader.getResource(location));
    }
}

接着做个测试,测试代码如下:

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

输出如下:

在这里插入图片描述

    捣鼓了半天,A君多少松了一口气,总算可以交差了。这回A君已经做好心里准备了,颤抖着双手把代码提交上去,坐等老大批评(开摆)

    老大又看了遍代码,语重心长的说:A君,你没有将bean定义提取出来,假如用户想在运行时修改bean定义,这时候怎么办?拿回去优化下吧

第五版 提取Bean配置信息

    得!这个老大不是个好糊弄的主,继续优化吧

    A君看着代码,琢磨了一会儿,心想:既然运行时可能修改bean定义,那就先把Bean定义缓存缓存起来,等到用的时候在缓存里边取就行了

    于是,A君定义了BeanDefinition类用来存储Bean定义,代码如下:

import lombok.Data;

/**
 * Bean配置信息类
 */
@Data
public class BeanDefinition {
    private String id;
    private String beanClass;
    private String scope;
    private Boolean lazyInit;
    private String initMethodName;
    private String destroyMethodName;
    private String description;
    private MutablePropertyValues properties;
}

Bean配置信息的实体类有了,还需要个地方缓存这个Bean信息才行。考虑到缓存可能有多种实现,A君又定义了BeanDefinitionRegistry接口,代码如下:

import com.hqd.ch03.v5.config.BeanDefinition;

/**
 * 缓存Bean定义信息
 */
public interface BeanDefinitionRegistry {
    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);

    void removeBeanDefinition(String beanName);

    BeanDefinition getBeanDefinition(String beanName);

    boolean containsBeanDefinition(String beanName);
}

有了BeanDefinitionRegistry接口之后还需要提供实现,默认用map实现即可,代码如下:

import com.hqd.ch03.v5.config.BeanDefinition;

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

/**
 * Bean信息缓存,map实现
 */
public class SimpleBeanDefinitionRegistry implements BeanDefinitionRegistry {
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();

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

    @Override
    public void removeBeanDefinition(String beanName) {
        beanDefinitionMap.remove(beanName);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) {
        return beanDefinitionMap.get(beanName);
    }

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

XmlBeanDefinitionReader需要新增构造器,用来接收传入的BeanDefinitionRegistry ,改造如下:

在这里插入图片描述

完整代码:


import com.hqd.ch03.v5.config.BeanDefinition;
import com.hqd.ch03.v5.config.MutablePropertyValues;
import com.hqd.ch03.v5.io.Resource;
import com.hqd.ch03.v5.registry.BeanDefinitionRegistry;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.util.List;

/**
 * XML方式读取Bean定义
 */
public class XmlBeanDefinitionReader implements BeanDefinitionReader {
    private BeanDefinitionRegistry beanDefinitionRegistry;

    public XmlBeanDefinitionReader(BeanDefinitionRegistry beanDefinitionRegistry) {
        this.beanDefinitionRegistry = beanDefinitionRegistry;
    }


    @Override
    public void loadBeanDefinition(Resource resource) {
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(resource.getInputStream());
            Element rootElement = document.getRootElement();
            List<Element> beanEles = rootElement.elements("bean");
            beanEles.forEach(beanEle -> {
                BeanDefinition bd = parseBeanEle(beanEle);
                beanDefinitionRegistry.registerBeanDefinition(bd.getId(), bd);
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 转换bean节点
     *
     * @param element
     * @return
     */
    private BeanDefinition parseBeanEle(Element element) {
        BeanDefinition bd = new BeanDefinition();
        bd.setScope(element.attributeValue("scope"));
        bd.setId(element.attributeValue("id"));
        String lazyInit = element.attributeValue("lazy-init");
        bd.setLazyInit(StringUtils.isBlank(lazyInit) ? true : Boolean.valueOf(lazyInit));
        bd.setBeanClass(element.attributeValue("class"));
        bd.setProperties(parsePropertyEles(element.elements("property")));
        return bd;
    }

    /**
     * 转换bean下的property节点
     *
     * @param elements
     * @return
     */
    private MutablePropertyValues parsePropertyEles(List<Element> elements) {
        if (CollectionUtils.isNotEmpty(elements)) {
            MutablePropertyValues propertyValues = new MutablePropertyValues();
            elements.forEach(element -> {
                propertyValues.addProperty(element.attributeValue("name"), element.attributeValue("value"));
            });
            return propertyValues;
        }
        return null;
    }
}

SpringImitation需要提供BeanDefinitionRegistry属性,创建bean时从缓存获取获取Bean配置信息。改造如下:

在这里插入图片描述

完整代码:


import com.hqd.ch03.v5.config.BeanDefinition;
import com.hqd.ch03.v5.config.MutablePropertyValues;
import com.hqd.ch03.v5.io.DefaultResourceLoader;
import com.hqd.ch03.v5.io.ResourceLoader;
import com.hqd.ch03.v5.registry.BeanDefinitionRegistry;
import com.hqd.ch03.v5.registry.SimpleBeanDefinitionRegistry;
import org.apache.commons.beanutils.BeanUtils;

public abstract class SpringImitationV5 {
    protected BeanDefinitionRegistry beanDefinitionRegistry = new SimpleBeanDefinitionRegistry();
    /**
     * 资源加载器
     */
    protected ResourceLoader resourceLoader = new DefaultResourceLoader();

    private void initBean(Object bean, BeanDefinition bd) {
        try {
            MutablePropertyValues properties = bd.getProperties();
            properties.getProperties().keySet().forEach(name -> {
                String value = properties.getValue(name);
                try {
                    BeanUtils.setProperty(bean, name, value);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private Object createBean(BeanDefinition bd) {
        try {
            String beanClass = bd.getBeanClass();
            Object o = Class.forName(beanClass).getConstructor().newInstance();
            initBean(o, bd);
            return o;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public <T> T getBean(String beanName, Class<T> clazz) {
        BeanDefinition bd = beanDefinitionRegistry.getBeanDefinition(beanName);
        return (T) createBean(bd);
    }


    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}

SpringImitationXml也需要改造下,创建XmlBeanDefinitionReader时候,需要传入Bean配置注册器,改造如下:

在这里插入图片描述

完整代码:

import com.hqd.ch03.v5.reader.XmlBeanDefinitionReader;

public class SpringImitationV5Xml extends SpringImitationV5 {

    private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanDefinitionRegistry);

    public SpringImitationV5Xml(String location) {
        this.reader.loadBeanDefinition(resourceLoader.getResource(location));
    }

}

做个简单的测试,测试代码如下:

	 /**
     * 提取Bean配置信息
     */
    @Test
    public void v5() {
        System.out.println("############# 第五版:提取Bean配置信息 #############");
        SpringImitationV5 bean = new SpringImitationV5Xml("classpath:v5/bean.xml");
        User user = bean.getBean("user", User.class);
        System.out.println("基于类路径加载:" + user);
    }

输出如下:

在这里插入图片描述

    做完这些,A君又看几遍,觉得没什么问题后,再次把代码提交上去。他不太相信老大还能提出问题

    老大这次没有马上提出问题,过了许久,才缓缓说道:XmlBeanDefinitionReader这个类做的事太多了,不论是Document的加载,解析,还是注册BeanDefinition都在里边做,类太过臃肿了。而且把XML文件加载成DOM可以有多种方式,你又把他写死了


总结

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

穷儒公羊

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

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

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

打赏作者

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

抵扣说明:

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

余额充值