type
status
date
slug
summary
tags
category
icon
password
开一个新坑!手写Spring!
这篇文章大量参考廖雪峰老师的手写Spring教程。事实上,就是这个教程的笔记。我一步一步地跟他做,也算是记录下自己探索和学习的过程吧。
这个博客的意义在于加深自己对spring底层原理的理解。同时,由于网络上大多手写spring的教程都是使用java实现的,我用kotlin实现也算为了弥补一下网上教程的空白吧。(虽然java无缝转kotlin)
为了搞点不一样的,咱也给自己写的spring框架起一个新名字——kotring🫐。
所有代码已在github同步,可以点点star喵~
📝 第一章:实现IO的基本封装
这里使用注解的方式实现对属性的注入。仿照Spring实现两个注解:
@Component
注解:class级别。标注了这个注解的class会被注入到kotring的IoC容器中。@Bean
注解:method级别。将方法的返回值注入Bean。@Value
注解:属性注入。初探:最简单的IoC容器实现方法(弃用)
这个是在没有阅读源码的情况下自己实现的一个简单的IoC容器,能用,但是不好用。后面重构了,删了可惜,就把代码和思路贴到这里来抛砖引玉。
项目结构很简单,思路也很简单。在定义以上三个注解之后,定义一个
AnnotationApplicationContext: ApplicationContext
进行上下文的管理即可。重构:实现io类的封装
这好吗?这不好。众所周知,Spring里面装载Bean是要读取Xml文件(虽然我并不打算做读取Xml文件的功能)、读取配置类(properties)的,这些文件统称为Resource文件。我们没有对Resource进行封装,这不利于我们之后注入属性等资源。我们必须对Resource进行一个封装。
Resource类
首先,Resource类,简单封装一下即可。Spring的Resource接口十分复杂,有各种各样的判断(比如是否为一个文件,是否可读等等),用以封装不同的Resource(比如File、Classpath下的资源和Byte Array等)。咱不做这么多,所以把Resource设置成data class而不是接口。
暂定俩属性:
- 路径。字符串,能够根据这个字符串找到它。比如:
C:\Users\Niyuta\IdeaProjects\kotring\target\classes\com\cheparity\kernel\core\annotation\Bean.class
- 名称。也是字符串,比如上面的路径那个Resource的名称就叫
Bean.class
ResourceSolver类
解析器,想实现这样一个功能:给定一个类路径
com.cheparity
,要能够扫描该类路径下的所有文件,把文件信息封装一下,再存到一个list中。这样,我们在做IoC容器的时候就可以指定一个特定的ResourceSolver,然后通过这个ResourceSolver获取到类路径下的所有文件,再进行注入Bean注入、属性注入等工作。需求有了,来几个例子🌰:
所以我们这么写。注意companion object里用了一些拓展函数进行简化。
PropertyResolver类
至于要实现的功能,廖雪峰老师写得非常好,在这里我就直接复制了嘿嘿。
支持的属性查询方式
- 按配置的key查询,例如:
getProperty("app.title")
;
- 以
${abc.xyz}
形式的查询,例如,getProperty("${app.title}")
,常用于@Value("${app.title}")
注入;
- 带默认值的,以
${abc.xyz:defaultValue}
形式的查询,例如,getProperty("${app.title:Summer}")
,常用于@Value("${app.title:Summer}")
注入。
这些功能在注入属性、获取属性时非常重要。
咱们先建立一个解析器,
PropertyResolver
类,用于解析属性。里面应该有什么呢?首先,property,说简单点,就是键值对,很自然地想到我们可以用一个map去存它。property长什么样?在这里用系统变量举几个例子。
那么我们调用
getProperty("windir")
或者 getProperty("\${windir}")
的时候就应该能够返回 C:\Windows
。储存的过程又是怎样的呢?其实Java给我们提供了天然的Property类。我们只需要简单看看文档,然后直接用人家写好的轮子即可。大致调用过程,应该长这样:
思路明确,开写!
我们需要能够把自定义的properties存进去,所以构造函数里得有
props
参数。到
getProperty
这里遇到了麻烦。诚然,直接getProperty("app.title")
比较容易,可是如果我要调用getProperty("${app.title}")
,更复杂地,要调用带有默认值的getProperty("${app.title:Summer}")
,阁下又该如何应对呢?后一种复杂类型的,我们不妨统称为属性表达式。我们可以对属性表达式进行一个封装。里面放一个语法分析的方法,通过调用这个方法,返回两部分:属性的键和属性的默认值(如果没有默认值就为空),完成属性表达式的语法解析。
❔为什么不在init的时候就完成解析,而是把parse过程放在companion object里
当以后拓展解析#开头等字符的功能时,我们调用不同函数即可,这其实是一个小型的工厂模式。放在构造函数里的话格局就小了。
然后我们就可以愉快地在
PropertyResolver
里去解析啦~大体思路
如果传参以$开头,说明是表达式,新建一个
PropertyExpr
使其完成解析;如果是朴素又平凡的字符串则直接从properties Map里查。里面用到了一个简单的递归。除此之外,spring还支持把属性值转化为你想要的变量。我们现在
properties: Map
里存的都是字符串,可以通过某个convert函数,把字符串转化为想要的类型。怎么实现?得有个转换规则嘛。所以定义一个converter集合再好不过了。然后,我们在
convert
函数里,就可以根据需要转换成的Class值,以及converter函数,来进行转换。结束结束结束!下一篇博客,将会正式开始实现IoC注入Bean的相关逻辑。
第一章结束后,我的项目结构是这样的。即将重构的文件还没删。
🤗 总结归纳
感叹一下,跟着廖老师的教程走,才知道Spring源码考虑的细节错综复杂,细致入微。对于我个人而讲,如果不了解底层原理直接使用,那么我写代码的过程是很痛苦的。
所以,希望写完我的Kotring之后,我能安心地使用Spring和SpringBoot这两个强大的工具。
📎 参考文章
Kotlin~ Kotring~ 🎶
- 作者:Niyuta
- 链接:https://www.niyuta.eu.org/article/spring/handwriting/1
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章