首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 软件管理 > 软件架构设计 >

做一个自个儿的AppServer-JDStream(三) IOC容器

2012-10-30 
做一个自己的AppServer-JDStream(三) IOC容器?????Guice和PicoContainer的比较???? ?感谢mineral在此系列

做一个自己的AppServer-JDStream(三) IOC容器

?????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的相同名称和参数的方法。

?????

????? 遇到的问题

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的实例)?

public static void main(String[] args) throws Exception{ Test test = new Test(); Test1 test13 = new Test3(); Test2 test2 = new Test4(); Test3 test3 = new Test3(); Test4 test4 = new Test4(); //(1) //test.tst(test3,test4); //(2) test.tst(test13, test4); Method m = test.getClass().getMethod("tst", new Class[]{Test1.class, Test4.class}); m.invoke(test, test13, test4); //输出test1 test4 m = test.getClass().getMethod("tst", new Class[]{Test3.class, Test2.class}); m.invoke(test, test13, test2); //输出 test3 test2 } 2 楼 javatracker 2008-12-31   Method m = test.getClass().getMethod("tst", new Class[]{Test1.class, Test4.class});   

关键问题在这里,一般从动态代理和cglib里面拦截后得到的参数事Object[] args,根据args[0].getClass()得到参数类型,如果此参数是Test3的实例只能得到Test3.class,得不到Test1.class.如果递归找superclass和interface,那样会牵涉到很多父类和父接口,如果此类中又有多个这些父类和父接口得重载方法,就会调用出错。因为在非反射调用里面是根据变量声明来判别得,而在此变量声明被范化成Object了,失去了声明类型。 3 楼 stone2oo6 2009-01-07   javatracker 写道
Method m = test.getClass().getMethod("tst", new Class[]{Test1.class, Test4.class});    关键问题在这里,一般从动态代理和cglib里面拦截后得到的参数事Object[] args,根据args[0].getClass()得到参数类型,如果此参数是Test3的实例只能得到Test3.class,得不到Test1.class.如果递归找superclass和interface,那样会牵涉到很多父类和父接口,如果此类中又有多个这些父类和父接口得重载方法,就会调用出错。因为在非反射调用里面是根据变量声明来判别得,而在此变量声明被范化成Object了,失去了声明类型。


参数是Test3的实例只能得到Test3.class, Test4实例参数能拿到Test4.class, 寻找方法参数为(Test3, Test4)的方法;若是不存在该方法,拿Test3.class的上一级接口类Test1.class与Test4.class组合寻找方法为(Test1, Test4);若还是找不着,则找Test4.class的上一级接口Test2.class与Test3.class组合...以此内推,直到找到方法或遍历所有接口为止。
缺点:复杂而且反射查找方法的次数太多约为M的N次方(n指参数个数,m指参数层次上实现的接口数)

是否可以约定参数只为上一级接口或自身实现类,这样复杂度只有2的N次方.

4 楼 javatracker 2009-01-07   stone2oo6 写道
参数是Test3的实例只能得到Test3.class, Test4实例参数能拿到Test4.class, 寻找方法参数为(Test3, Test4)的方法;若是不存在该方法,拿Test3.class的上一级接口类Test1.class与Test4.class组合寻找方法为(Test1, Test4);若还是找不着,则找Test4.class的上一级接口Test2.class与Test3.class组合...以此内推,直到找到方法或遍历所有接口为止。 缺点:复杂而且反射查找方法的次数太多约为M的N次方(n指参数个数,m指参数层次上实现的接口数) 是否可以约定参数只为上一级接口或自身实现类,这样复杂度只有2的N次方.


感谢回复,你说的不错,是可以约定上一级接口或superclass,这样虽然失去了广泛性,但也不失为一种实现。目前基本也就采用这种方法。另外一种方法就是你说的递归向上查找,但找到的不一定是方法中声明的超类,也是一种不太准确的方法。IOC中基本采用这两种方法,虽然有点问题,但能满足一般需要。

另外现在正写的RMI工厂也涉及到这个问题,但这里会多几种实现,你上面说的约定接口,让使用者自己分发,这是最高效的一种,同时也增加了使用成本。另外还准备提供一种就是JDK6的新特性-动态编译,当这个实例注册是就动态生成它的代理类,根据方法的toString()(一般各个方法都不同)来分发到实例的各个方法。之所以用动态编译,是为了提高效率,抛弃反射。

热点排行