照虎画猫写自己的Spring

从细节跳出来

看了部分Spring的代码,前面用了四篇内容写了一些读书笔记。 回想起来,论复杂度,Spring够喝上好几壶的。他就像一颗枝繁叶茂的大树,远处看,只是一片绿;走近看,他为你撑起一片小天地,为你遮风避雨;往深了看,他盘根错节,根基夯实。

在看Spring代码的过程中,我几度有些迷糊,因为一行简单的函数调用,你要是一直跟踪下去,从一个函数跳到另一个函数,又从一个类进入到另一个接口或者代理类,可能原本你只想知道函数做了什么,等回过头来,你发现已经找不到回去的路……

所以,每写一篇的时候,我都用一两句话总结该篇主要讲的是Spring干了什么事,实现了什么功能。

这些,我觉得还不够。所以,今天我照虎画猫,写了一个自己的Spring——Fairy项目。

Fairy项目

取名Fairy,意为小精灵,象征着东西不大,但是能量无穷,稍有契合Spring春天生机盎然之意。

大体思路

Spring经过这么多年的发展和补充,已经变成庞然大物,代码中包含了很多可扩展性和抽象的代码和设计。如果想一把抓尽收眼底,还是比较难消化的。这里,就设计一个简洁版的Spring,也算是抽丝剥茧,看看Spring最开始是给我们解决了一个什么问题,大体思路如下:

  • 声明配置文件,用于声明需要加载使用的类
  • 加载配置文件,读取配置文件
  • 解析配置文件,需要将配置文件中声明的标签转换为Fairy能够识别的类
  • 初始化类,提供配置文件中声明的类的实例

项目结构如下:

声明配置文件

首先你需要声明一个配置文件,这是一切工作的开始(当然了,更首先是要有一个项目,后面会给出在GitHub上的项目地址)。所有你想要用到的类,都应该声明在这里。

配置文件的好处就是可扩展性强,耦合度低。当需要声明一个bean的时候,我们只需要打开配置文件,在其中加上一个标签,填充你需要使用的那个类即可,剩下的工作就交个容器。

这里的配置文件application-context.xml很简单

<beans>
    <bean id="fairyBean" class="com.jackie.fairy.bean.impl.FairyBeanImpl">
    </bean>
</beans>

看完配置,我们大概就知道这是一次想得到FairyBeanImpl这个类的实例的征程。按照以往的套路,这些交给Spring去执行就好了,大可以通过这种xml配置的方式,甚至可以通过@Autowired注解的方式

只是这里,我们不再引入Spring的任何依赖,我们要自己造轮子,完成这次bean的加载。这里的标签其实可以声明任何你想声明的标签名,因为已经跳出Spring的约束了,好比这样

<life>
    <smile id="fairyBean" class="com.jackie.fairy.bean.impl.FairyBeanImpl">
    </smile>
</life>

加载、解析配置文件

从上面的声明可以看出,我们还是使用了XML这种传统的配置文件的方式(后面还会尝试使用JSON的数据格式,详见项目中的JsonParserImpl)。 所以加载首先我们需要加载xml配置文件。其实这里加载xml文件和其他格式的文件并无二致,只是在解析的时候才有差别。 加载

URL xmlPath = XmlReaderUtil.class.getClassLoader().getResource(fileName);

这里只需要传入文件名,剩下的通过getResource得到文件的URL路径,后面的事情就交给xml解析器去做了。

解析 因为配置文件是xml格式,所以需要针对xml进行解析,这里用的是dom4j对xml进行解析。解析的本质就是层层剥开,抽取想要的信息。 我将解析的过程写在工具类中

public class XmlReaderUtil {
    private static final Logger LOG = LoggerFactory.getLogger(XmlReaderUtil.class);

    public static List<BeanDefinition> readXml(String fileName) {
        List<BeanDefinition> beanDefinitions = Lists.newArrayList();

        //创建一个读取器
        SAXReader saxReader = new SAXReader();
        Document document = null;

        try {
            //获取要读取的配置文件的路径
            URL xmlPath = XmlReaderUtil.class.getClassLoader().getResource(fileName);
            //读取文件内容
            document = saxReader.read(xmlPath);
            //获取xml中的根元素
            Element rootElement = document.getRootElement();

            for (Iterator iterator = rootElement.elementIterator(); iterator.hasNext(); ) {
                Element element = (Element)iterator.next();
                String id = element.attributeValue("id");
                String clazz = element.attributeValue("class");
                BeanDefinition beanDefinition = new BeanDefinition(id, clazz);
                beanDefinitions.add(beanDefinition);
            }

        } catch (Exception e) {
            LOG.error("read xml failed", e);
        }

        return beanDefinitions;
    }
}

主要过程

  • 新建一个解析器
  • 加载xml配置文件
  • 找到根元素
  • 遍历各个元素
  • 找到相应的属性
  • 完成解析,将信息存储到集合中

初始化类

完成配置文件的解析后,就需要针对配置文件的信息进行实例化,方便调用者使用。 通过解析后,我们得到了一个List集合,存放了BeanDefinition,每一个BeanDefinition都存放这标签的属性值(这里仅支持id和class属性的解析和存储)。下面就需要针对List集合中解析后的标签进行实例化了。

private void instanceBeanDefinitions() {
    if (CollectionUtils.isNotEmpty(beanDefinitions)) {
        for (BeanDefinition beanDefinition : beanDefinitions) {
            if (StringUtils.isNotEmpty(beanDefinition.getClassName())) {
                try {
                    instanceBeans.put(beanDefinition.getId(),
                            Class.forName(beanDefinition.getClassName()).newInstance());
                    LOG.info("instance beans successfully, instanceBeans: {}", instanceBeans);
                } catch (InstantiationException e) {
                    LOG.error("instantiation failed", e);
                } catch (IllegalAccessException e) {
                    LOG.error("illegalAccessException", e);
                } catch (ClassNotFoundException e) {
                    LOG.error("classNotFoundException", e);
                }
            }
        }
    }
}

主要是通过遍历解析得到的集合,分别对各个元素一一进行实例化,再存储到Map集合中,方便后面根据名称获取(这里还有一些异常情况的处理和参数校验就不做解释,可以直接看代码)。

测试

完成以上简单的几步之后,我们就可以测试成果了,新建测试类FairyTest

@Test
public void testLoadBean() {
    FairyApplicationContext applicationContext = new FairyApplicationContext("application-context.xml", ParseType.XML_PARSER);
    FairyBean fairyBean = (FairyBean) applicationContext.getBean("fairyBean");
    fairyBean.greet();
}

这样,我们就如愿的完成了FairyBean类的加载和实例化,我们没有用到Spring的任何依赖,自己写了个小容器完成了类加载。 项目地址:https://github.com/DMinerJackie/fairy

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏battcn

一起来学SpringBoot | 第十八篇:轻松搞定全局异常

实际项目开发中,程序往往会发生各式各样的异常情况,特别是身为服务端开发人员的我们,总是不停的编写接口提供给前端调用,分工协作的情况下,避免不了异常的发生,如果直...

1342
来自专栏Java编程技术

Velocity引擎原理探究

常见的Java模板引擎有JSP、Freemark,Velocity。在MVC三层框架中,模板引擎属于view层,实质是把model层内容展现到前台页面的一个引擎...

1632
来自专栏郭霖

Android Volley完全解析(三),定制自己的Request

经过前面两篇文章的学习,我们已经掌握了Volley各种Request的使用方法,包括StringRequest、JsonRequest、ImageRequest...

2346
来自专栏犀利豆的技术空间

徒手撸框架--实现 RPC 远程调用

微服务已经是每个互联网开发者必须掌握的一项技术。而 RPC 框架,是构成微服务最重要的组成部分之一。趁最近有时间。又看了看 dubbo 的源码。dubbo 为了...

1532
来自专栏androidBlog

一步步拆解 LeakCanary

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/de...

961
来自专栏JackieZheng

照虎画猫写自己的Spring

从细节跳出来 看了部分Spring的代码,前面用了四篇内容写了一些读书笔记。 回想起来,论复杂度,Spring够喝上好几壶的。他就像一颗枝繁叶茂的大树,远处看...

2076
来自专栏Java架构师历程

spring boot应用启动原理分析

摘要: spring boot quick start 在spring boot里,很吸引人的一个特性是可以直接把应用打包成为一个jar/war,然后这个jar...

8073
来自专栏pangguoming

SpringBoot实战 之 异常处理篇

在互联网时代,我们所开发的应用大多是直面用户的,程序中的任何一点小疏忽都可能导致用户的流失,而程序出现异常往往又是不可避免的,那该如何减少程序异常对用户体验的影...

4307
来自专栏一枝花算不算浪漫

[Java 缓存] Java Cache之 Guava Cache的简单应用.

4056
来自专栏函数式编程语言及工具

Akka(6): become/unbecome:运算行为切换

   通过一段时间的学习了解,加深了一些对Akka的认识,特别是对于Akka在实际编程中的用途方面。我的想法,或者我希望利用Akka来达到的目的是这样的:作为传...

3039

扫码关注云+社区

领取腾讯云代金券