Guice和PicoContainer的比较
感谢mineral在此系列博客的上一篇当中推荐的IOC容器Guice,没来得及看源码,初步了解了一下,发现Guice确实有自己的独到之处,它和Spring的最大区别应该是构造够轻量,设计够巧妙,配置够简单,但它也有很多自己的不足(个人观点)。上一篇博客站在JDStream使用的角度简单的比较了Spring、JBoss MicroContainer、PicoContainer和JFox优缺点,决定采用PicoContainer作为JDStream的IOC容器。由于以前没有接触过Guice,今天看了些介绍后就Guice和PicoContainer做个比较,不足之处希望能得到各位大侠的指教。
1、Guice有三种注入方式:Field、contructor和setter;PicoContainer同样也有这三种注入方式;
2、Guice使用Inject注释根据业务接口来注入Bean,如此减少了XML配置的繁杂,同时又不像其它IOC的Annotation注入那样繁琐,任何Bean的注入只需@Inject即可。但对于一个业务接口有多个实现的情况,单凭接口并不能够定位到相应的Bean,Guice需要以下辅助配置来定位:
A:通过在业务接口上添加@ImplementedBy注释来指定实现类;
B:通过实现Module接口硬编码,将实现类绑定到接口,同时用自定义Annotation来作为分支标记或自定义一个字符串作为分支标记;
C:可以在@Inject的下面用@Name来指定实现类;
同时Guice还可以通过实现Provider接口来动态注入注入;
而PicoContainer也可以通过@Inject注入,对于一个接口或超类多个子类的情况也是通过自定义Annotation来区分;
3、其它方面PicoContainer有Bean的监视器、生命周期管理、Container父子级联等实现,Guice可能也有这方面的实现,没有做深入的研究。
4、Guice有更多的注释,同时又分绑定作用域,这个和PicoContainer的父子级联比较相似,都是实现实例的分割,相当于全局变量和局部变量。而PicoContainer只有Inject、Bind、Cache三个注释,并且都不带参数;
由上面的比较基本可以看出PicoContainer有点像Guice的精简版,它更多的是通过Container接口将功能暴露给使用者,在外层并没有做太多的封装,它比Guice更轻量,更适合嵌入其它容器框架。而Guice更适合独立作为一个轻量的IOC框架存在。另外从JDStream的需求来看PicoContainer也更适合被集成于其中,具体原因后面阐述。
IOC容器的原理
比较了上面两个IOC容器后再看IOC容器的原理:
IOC容器其实就是将一个类拆分了后和需要注入的参数及配置的操作缓存起来,大部分的IOC容器都逃不出以下几步:
1、根据配置文件找到类名称,用加载器加载;
2、用反射从类中分离出Field,Constructor,Method,Annotation,Interface,SuperClass等等分类缓存;
3、缓存需要注入的参数;
4、根据配置文件配置的操作或其它程序的调用,用Constructor生成Object,注入参数,此实例或缓存,或不缓存,或用弱引用缓存;
5、生命周期操作,Annotation操作;
从原理上说IOC容器和JMX实现没有太大的区别,JMX其实也算是一种IOC容器。但从实现上来说IOC容器比JMX灵活的多,它不必考虑J2EE规范的限制,完全自由发挥,可以设计的如Spring般繁杂,也可以设计的如PicoContainer般小巧。它可以使自己管理的Bean更加POJO,不必再亲自去实现框架管理接口,而是由框架生成被管理Bean的子类或业务接口的实现类作为代理去实现管理接口,然后一切业务调用都从代理接口传入,再去完成被管理Bean的调用。Spring和EJB就是这样管理的。这中间可以注册监视器来监控Bean的任何状态变化,注册拦截器来监控外部对Bean的操作。
IOC容器给人的最大好处就是灵活多变,耦合松散,但这样的结果也是有代价的,首先越灵活配置越复杂,大量的XML和Annotation也会成为灾难,其次大量的动态代理、反射、CGLIB、拦截器、监视器等管理手段的使用,极大的降低了效率,对高并发环境下的应用是不可以接受的。JDStream的一个最重要的设计准则是在高并发通路上尽量避免使用以反射为基础的操作,以此来提高效率。
由于JDStream偏向于高并发处理能力的需要,对上面的优缺点做了平衡,那就是插件的入口Bean类和插件内部的某些Bean类(需要对其管理但又不在高并发通路上的)以及业务程序里面的一些Bean类(同样需要对其管理但又不在高并发通路上的)使用IOC容器管理,需要对配置文件热修改以及对内部监控提供视图的Bean类用JMX容器管理,其它的Bean类并不提供框架管理功能。
JDStream的IOC容器实现
现在说说为什么PicoContainer更适合JDStream。首先JDStream的IOC容器最主要的职责是管理插件入口的Bean类,这些Bean的相互依赖不多,而且都肩负着插件配置参数的输入(如IP地址、端口号等),这些参数在部署后要经常修改,不可能采用Annotation注入,也不可能硬编码注入,只能用XML注入,所以Guice的Annotation就用不上了;其次JDStream的另一个职责是管理业务模块的一些Bean,这些Bean上面将标注属于JDStream自己的Annotation,这些Annotation的解析应该放在Bean的注册过程中,因此扩充PicoContainer更好一点;再次Guice管理的Bean基本上都要实现接口,这点和框架有点耦合;最后最重要的一点是PicoContainer的XML解析器已经完成了大部分了,目前先这样写下去,以后发觉不恰当的话在更换。
上面的比较说明,就是对JDStream的IOC容器的基本要求。首先从XML解析开始,由于配置文档不大,所以采用Dom4j的dom方式解析配置文件。为了做到与框架完全无关,就不能实现框架的任何接口,但PicoContainer管理Bean的生命周期又必须实现生命周期接口,这样就需要像上面说的那么样框架生成一个代理。代理有两种生成方式:动态代理和CGLIB,动态代理要求被管理的Bean必须有接口实现,此处似乎对被管理的Bean要求多了点,所以考虑用CGLIB生成被管理Bean的子类,同时这个子类实现框架的生命周期接口,然后以无参数的构造方法生成实例来作为代理(此处要求被管理的Bean必须提供无参数的构造方法),然后以XML配置文件中配置的Bean的名称为注册名注册到PicoContainer中,实际要注册的Bean按配置文件构造实例,注入参数,以配置的Bean名称加一固定字符串注册到PicoContainer中。当框架调用代理的生命周期接口时,代理委托给配置文件中定义Bean的相应的生命周期方法,当业务程序调用代理的业务方法时,代理委托给Bean的相同名称和参数的方法。
遇到的问题
这个地方出现了问题,由于上面委托给Bean的方法是拦截所得,只知道实例、方法和参数,在Bean中有多个同名的重载方法时,可能无发判断到底调用哪一个方法。看如下代码:
public class Test{
public static void main(String[] args){
Test test = new Test();
Test1 test13 = new Test3();
Test3 test3 = new Test3();
Test4 test4 = new Test4();
//(1)
test.tst(test3,test4);
//(2)
test.tst(test13, test4);
}
public void tst(Test1 test1,Test4 test4){
System.out.println("test1 test4");
}
public void tst(Test3 test3,Test2 test2){
System.out.println("test3 test2");
}
}
public interface Test1{
}
public interface Test2{
}
public class Test3 implements Test1{
}
public class Test4 implements Test2{
}
上面注释(1)下面的调用编译通不过,说明sun也无法判断到底要调用Test哪个tst重载方法,可以不去管它。
注释(2)下面的调用根据变量声明,能调用到下面的方法:
public void tst(Test1 test1,Test4 test4){
System.out.println("test1 test4");
}
但如果要通过反射调用此方法,即:知道Test的一个实例是test,方法名是tst,两个参数是两个Object实例,参数类分别是Test3和Test4,这样如何完成test.tst(test13, test4)这样的调用(比如此方法用动态代理送过来后test13和test4失去原有Test1和Test4的声明,只知道他们是被声明为Object的实例)?