文档章节

设计一个可拔插的 IOC 容器

crossoverJie
 crossoverJie
发布于 11/15 08:39
字数 1541
阅读 1154
收藏 50

前言

磨了许久,借助最近的一次通宵上线 cicada 终于更新了 v2.0.0 版本。

之所以大的版本号变为 2,确实是向下不兼容了;主要表现为:

  • 修复了几个反馈的 bug
  • 灵活的路由方式。
  • 可拔插的 IOC 容器选择。

其中重点是后面两个。

新的路由方式

先来看第一个:路由方式的更新。

在之前的版本想要写一个接口必须的实现一个 WorkAction;而且最麻烦的是一个实现类只能做一个接口。

因此也有朋友给我提过这个 issue


于是改进后的使用方式如下:

是否有点似曾相识的感觉。

如上图所示,不需要实现某个特定的接口;只需要使用不同的注解即可。

同时也支持自定义 pojo, cicada 会在调用过程中对参数进行实例化。

拿这个 getUser 接口为例,当这样请求时这些参数就会被封装进 DemoReq 中.

http://127.0.0.1:5688/cicada-example/routeAction/getUser?id=1234&name=zhangsan

同时得到响应:

{"message":"hello =zhangsan"}

实现过程也挺简单,大家查看源码便会发现;这里贴一点比较核心的步骤。

  • 扫描所有使用 @CicadaAction 注解的类。
  • 扫描所有使用 @CicadaRoute 注解的方法。
  • 将他们的映射关系存入 Map 中。
  • 请求时根据 URLMap 中查找这个关系。
  • 反射构建参数及方法调用。

扫描类以及写入映射关系


请求时查询映射关系


反射调用这些方法

是否需要 IOC 容器

上面那几个步骤其实我都是一把梭写完的,但当我写到执行具体方法时感觉有点意思了。

大家都知道反射调用方法有两个重要的参数:

  • obj 方法执行的实例。
  • args.. 自然是方法的参数。

我第一次写的时候是这样的:

method.invoke(method.getDeclaringClass().newInstance(), object);

然后一测试,也没问题。

当我写完之后 review 代码时发现不对:这样这里每次都会创建一个新的实例,而且反射调用 newInstance() 效率也不高。

这时我不自觉的想到了 Spring 中 IOC 容器,和这里场景也非常的类似。

在应用初始化时将所有的接口实例化并保存到 bean 容器中,当需要使用时只需要从容器中获取即可。

这样只是会在启动时做很多加载工作,但造福后代啊。

可拔插的 IOC 容器

于是我打算自己实现一个这样的 bean 容器。

但在实现之前又想到一个 feature:

不如把实现 bean 容器的方案交给使用者选择,可以选择使用 bean 容器,也可以就用之前的每次都创建新的实例,就像 Spring 中的 prototype 作用域一样。

甚至可以自定义容器实现,比如将 bean 存放到数据库、Redis 都行;当然一般人也不会这么干。

SPI 的机制也有点类似。

要实现上述的需求大致需要以下步骤:

  • 一个通用的接口,包含了注册容器、从容器中获取实例等方法。
  • BeanManager 类,由它来管理具体使用哪种 IOC 容器。

所以首先定义了一个接口;CicadaBeanFactory:

包含了注册和获取实例的接口。

同时分别有两个不同的容器实现方案。

默认实现;CicadaDefaultBean

也就是文中说道的,每次都会创建实例;由于这种方式其实根本就没有 bean 容器,所以也不存在注册了。

接下来是真正的 IOC 容器;CicadaIoc

它将所有的实例都存放在一个 Map 中。

当然也少不了刚才提到的 CicadaBeanManager,它会在应用启动的时候将所有的实例注册到 bean 容器中。

重点是图中标红的部分:

  • 需要根据用户的选择实例化 CicadaBeanFactory 接口。
  • 将所有的实例注册到 CicadaBeanFactory 接口中。

同时也提供了一个获取实例的方法:

就是直接调用 CicadaBeanFactory 接口的方法。


然后在上文提到的反射调用方法处就变为:

bean 容器中获取实例了;获取的过程可以是每次都创建一个新的对象,也可以是直接从容器中获取实例。这点对于这里的调用者来说并不关心

所以这也实现了标题所说的:可拔插

为了实现这个目的,我将 CicadaIoc 的实现单独放到一个模块中,以 jar 包的形式提供实现。

所以如果你想要使用 IOC 容器的方式获取实例时只需要在你的应用中额外加入这个 jar 包即可。

<dependency>
    <groupId>top.crossoverjie.opensource</groupId>
    <artifactId>cicada-ioc</artifactId>
    <version>2.0.0</version>
</dependency>

如果不使用则是默认的 CicadaDefaultBean 实现,也就是每次都会创建对象。

这样有个好处:

当你自己想实现一个 IOC 容器时;只需要实现 cicada 提供的 CicadaBeanFactory 接口,并在你的应用中只加入你的 jar 包即可。

其余所有的代码都不需要改变,便可随意切换不的容器。

当然我是推荐大家使用 IOC 容器的(其实就是单例),牺牲一点应用启动时间带来后续性能的提升是值得的。

总结

cicada 的大坑填的差不多了,后续也会做一些小功能的迭代。

还没有关注的朋友赶紧关注一波:

https://github.com/TogetherOS/cicada

PS:虽然没有仔细分析 Spring IOC 的实现,但相信看完此篇的朋友应该对 Spring IOC 以及 SpringMVC 会有一些自己的理解。

你的点赞与分享是对我最大的支持

© 著作权归作者所有

共有 人打赏支持
crossoverJie
粉丝 565
博文 74
码字总数 138725
作品 0
江北
后端工程师
私信 提问
加载中

评论(6)

crossoverJie
crossoverJie

引用来自“yong9981”的评论

小建议啊:@CicadaRoute("getuser")中的getUser可以省略,因为它与方法名是重名的,参数只有当不重名时才有必要。再进一步,如果如果方法名约定用"_"号开头,@CicadaRoute注解也可以省略掉。
另外IOC工具还支持另一种用法: getBean(xxx.class),它支持重构,常用于单例,Guice/Spring/jBeanBox(本人出品,了解一下)都支持。
嗯,API 后续可以再完善一下。
crossoverJie
crossoverJie

引用来自“蓝水晶飞机”的评论

你的 DEMO 写得可以👍 但要注意一下:1、Action 务必支持Http Method;2、 IOC 注册的 Map 必须是 ConcurrentHashMap 否则遇到两个线程并发就会异常。
HashMap 以及可以了,可以看下源码,加载所有的 bean 是单线程,没必要用 ConcurrentHashMap。

这个 pr 有讨论:

https://github.com/TogetherOS/cicada/pull/33
yong9981
yong9981
小建议啊:@CicadaRoute("getuser")中的getUser可以省略,因为它与方法名是重名的,参数只有当不重名时才有必要。再进一步,如果如果方法名约定用"_"号开头,@CicadaRoute注解也可以省略掉。
另外IOC工具还支持另一种用法: getBean(xxx.class),它支持重构,常用于单例,Guice/Spring/jBeanBox(本人出品,了解一下)都支持。
蓝水晶飞机
蓝水晶飞机
你的 DEMO 写得可以👍 但要注意一下:1、Action 务必支持Http Method;2、 IOC 注册的 Map 必须是 ConcurrentHashMap 否则遇到两个线程并发就会异常。
开源中国首席技术官
开源中国首席技术官
当然也认真看完了,感谢分享! ��
开源中国首席技术官
开源中国首席技术官
漏了一个字。
“便可随意切换不的容器。”
我心中的核心组件(可插拔的AOP)~大话开篇及目录

核心组件 我心中的核心组件,核心组件就是我认为在项目中比较常用的功能,如日志,异常处理,消息,邮件,队列服务,调度,缓存,持久化,分布式文件存储,NoSQL存储,IoC容器,方法拦截等等...

mcy247
2017/12/05
0
0
IoC组件~Autofac将多实现一次注入,根据别名Resove实例

对于IoC容器来说,性能最好的莫过于Autofac了,而对于灵活度来说,它也是值得称赞的,为了考虑系统的性能,我们经常是在系统初始化于将所有依赖注册到容器里,当需要于根据别名把实现拿出来,...

mcy247
2017/12/06
0
0
Spring的IOC原理[通俗解释一下]

IoC理论的背景 我们都知道,在采用面向对象方法设计的软件系统中,它的底层实现都是由N个对象组成的,所有的对象通过彼此的合作,最终实现系统的业务逻辑。 图1:软件系统中耦合的对象 如果我...

k_k_anna
2015/04/07
0
1
我心中的核心组件(可插拔的AOP)~第十三回 实现AOP的拦截组件Unity.Interception

说在前 本节主要说一下Unity家族里的拦截组件,对于方法拦截有很多组件提供,基本上每个Ioc组件都有对它的实现,如autofac,它主要用在orchard项目里,而castle也有以拦截的体现,相关可以看我的C...

mcy247
2017/12/05
0
0
Spring的IOC原理[通俗解释一下]

本文系转载,个人认为,这篇文章对IOC原理讲的比较清楚,通俗易懂,值得一看。 原文地址:http://blog.csdn.net/m13666368773/article/details/7802126 1. IoC理论的背景 我们都知道,在采用...

Alan丶Wang
2015/04/13
0
0

没有更多内容

加载失败,请刷新页面

加载更多

java 中间变量缓存机制(i++,++i)

public class Test { public static void main(String[] args) { int i = 0; i = i ++ ; System.out.println(i); } } 答案是 0 如果是 i = ++......

shzwork
4分钟前
0
0
初识多线程及其原理-笔记

什么情况下应该使用多线程? 通过并行计算提高程序执行性能 需要等待网络、I/O响应导致耗费大量的执行时间, 可以采用异步线程的方式来减少阻塞 tomcat7 以前的io模型 客户端阻塞 线程级别阻...

Java搬砖工程师
15分钟前
0
0
webpack4 css打包、压缩、分离、去重等优化配置详解

Webpack 4 目前版本已经到了4.27 相较于前面的版本还是有一些改动的,具体可以参考升级指南 迁移到新版本 这里只介绍 css 的 打包、压缩、分离、去重 的方法; 不懂的或者有更优的方案,欢迎...

zerokb-小浪
19分钟前
0
0
基于协同过滤算法的推荐

基于协同过滤算法的推荐 (本实验选用数据为真实电商脱敏数据,仅用于学习,请勿商用) 数据挖掘的一个经典案例就是尿布与啤酒的例子。尿布与啤酒看似毫不相关的两种产品,但是当超市将两种产...

阿里云官方博客
27分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部