我第一次写的时候是这样的:
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
容器的(其实就是单例),牺牲一点应用启动时间带来后续性能的提升是值得的。