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容器,能用,但是不好用。后面重构了,删了可惜,就把代码和思路贴到这里来抛砖引玉。
notion image
项目结构很简单,思路也很简单。在定义以上三个注解之后,定义一个 AnnotationApplicationContext: ApplicationContext 进行上下文的管理即可。

重构:实现io类的封装

这好吗?这不好。众所周知,Spring里面装载Bean是要读取Xml文件(虽然我并不打算做读取Xml文件的功能)、读取配置类(properties)的,这些文件统称为Resource文件。我们没有对Resource进行封装,这不利于我们之后注入属性等资源。我们必须对Resource进行一个封装。

Resource类

首先,Resource类,简单封装一下即可。Spring的Resource接口十分复杂,有各种各样的判断(比如是否为一个文件,是否可读等等),用以封装不同的Resource(比如File、Classpath下的资源和Byte Array等)。咱不做这么多,所以把Resource设置成data class而不是接口。
暂定俩属性:
  1. 路径。字符串,能够根据这个字符串找到它。比如: C:\Users\Niyuta\IdeaProjects\kotring\target\classes\com\cheparity\kernel\core\annotation\Bean.class
  1. 名称。也是字符串,比如上面的路径那个Resource的名称就叫 Bean.class

ResourceSolver类

解析器,想实现这样一个功能:给定一个类路径 com.cheparity ,要能够扫描该类路径下的所有文件,把文件信息封装一下,再存到一个list中。这样,我们在做IoC容器的时候就可以指定一个特定的ResourceSolver,然后通过这个ResourceSolver获取到类路径下的所有文件,再进行注入Bean注入、属性注入等工作。
需求有了,来几个例子🌰:
所以我们这么写。注意companion object里用了一些拓展函数进行简化。

PropertyResolver类

怎么那么多类啊!
至于要实现的功能,廖雪峰老师写得非常好,在这里我就直接复制了嘿嘿。
支持的属性查询方式
  1. 按配置的key查询,例如:getProperty("app.title");
  1. ${abc.xyz}形式的查询,例如,getProperty("${app.title}"),常用于@Value("${app.title}")注入;
  1. 带默认值的,以${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的相关逻辑。
第一章结束后,我的项目结构是这样的。即将重构的文件还没删。
notion image

🤗 总结归纳

感叹一下,跟着廖老师的教程走,才知道Spring源码考虑的细节错综复杂,细致入微。对于我个人而讲,如果不了解底层原理直接使用,那么我写代码的过程是很痛苦的。
所以,希望写完我的Kotring之后,我能安心地使用Spring和SpringBoot这两个强大的工具。

📎 参考文章

 
💡
Kotlin~ Kotring~ 🎶
焦虑小记spring-security基本配置
Niyuta
Niyuta
变分无限,孤心测度有同伦
公告
type
status
date
slug
summary
tags
category
icon
password
🎉热烈庆祝Niyuta拥有了个人网站!🎉
-- 感谢支持喵:) ---
👏网站正在建设中,有bug望不吝反馈赐教~👏
(迟早要重构掉