|
翻译:javavsnet 审校:BlueDavy
几周以前我描述了“hibernate”问题,怎样让多个bundle独立的使用Hibernate会话工厂而不需要互相了解。这些邮件不仅仅在寻找使用目前的规范来解决目前的问题的实践方案。这些邮件是我的研究课题。我在力图保持将来的OSGI规范干净而且符合正确的精神,所以我在玩弄选择权。下一次我会努力更加讲究实际。
我找到的共享会话工厂的解决方案是基于服务的,它允许bundle参与Hibernate的领域对象,而且它允许bundle使用一个领域对象的集合。然而,在这个解决方案中仍然潜伏着一个由于Hibernate特有的习惯导致的类装载的问题。在解决方案中这个问题通过一个简单的Require-Bundle来解决。然而,尽管这个方案可以工作,它很容易因为所需要的类重构进入其它bundle而崩溃。所以对于目前来说,这个方案足够好,对于将来我们需要理解根源问题并提出对这个根源问题的解决方案。让我们深入这个问题并分析正在发生着什么。
当Hibernate创建一个会话工厂,它实际上创建了一个实现类的动态代理类。这在Hibernate中不是一个问题,虽然事实上它使用了客户的类装载器来创建代理。因此客户必须对于它没有使用的类有可见性。这就是说,Hibernate假设所有的类自己知道它对用户是可见的。在一个模块化的系统中,这个假设显然不成立的。一个简单的修正是让客户bundle使用Require-Bundle来访问Hibernate bundle,然而Require-Bundle有它自己的一系列问题,参见OSGi R4.1 规范。
我们可以搁浅工作然后告诉Hibernate和我们一起行动。然而,对于我们中的绝大多数来说这不是很有建设性的姿态。尽管OSGI技术日益普及,目前大多数JAR不是为彻底的模块化准备的。
这些类型的类装载问题是地方性的,因为在Java中缺乏象OSGI服务注册这样的核实的扩展机制,所以有很多开发者使用类装载器来实现本地扩展机制。当需要在虚拟机中共享相同的包的多个版本时,所有这些本地机制会崩溃。这些本地机制也不能提供类空间的一致性,而一致性对于可靠性是重要的。因此,最佳解决方案时OSGI规范直接提出这些议题。
OSGi CPEG要求我对于这个问题提出一个OSGi RFP,所以我和我最喜爱的OSGi邀请学者:Richard S. Hall做了交流。我们做了一些头脑风暴并在过去的几星期中开发了原型以便更好的理解问题。Richard修改了Apache Felix来支持我们的试验,我使用这些修改做了一个基于web的取便条应用,它使用了Hibernate。
我们使用的第一种方法时声明性“implicit-wire”指令。我们采用这个方案是因为问题的深层结构是: 当你导入一个包,其结果是你应当导入附件的包。在Hibernate的例子中,当你导入了org.hibernate.cfg,你应该也导入net.sf.cglib.*,即使你没有直接的依赖于这些包。Richard因此为导出包的子句创建了一个x-implicitwire指令。这条指令的值是当任何bundle导入该包时,一系列应该被附加导入的包。例如:
Export-Package:
org.hibernate.cfg;x-implicitwire:=”net.sf.cglib.proxy,net.sf.cglib.core,net.sf.cglib.reflect, org.hibernate.proxy”
如果Apache Felix依赖了org.hibernate.cfg包,它现在可以通过x-implicitwire指令自动导入需要的包,从而确保当Hibernate试图为会话工厂创建一个代理时,在做导入的bundle可以看到恰当的包。
声明式方法非常简单而且确实解决了问题。原型代码,一个简单的基于web的便条程序,工作良好。唯一需要变化的是创建hibernate bundle的bnd文件。然而,我们不确信这个方案可以解决所有的问题,在很多情况下带有x-implicitwire的声明式方法是不可行的。因为需要的隐含的依赖事先并不知道。一个关键的例子是普通的面向方面的编程,其附加的类是完全开放的。就是说,一个AspectJ程序可以在类中织入任何可能的类。
对这一问题的唯一解决方案是允许其它bundle在运行时添加导入。经过一些讨论后,Richard找到了勇气和时间,在bundle上下文对象中增加一个addRequire功能。用这种方式增加的导入与DynamicImport-Package 子句一样对待。就是说,直到导入被发现,这些导入才最后被查阅。
我创建了一个简单的扩展bundle,当其它bundle被解析时可以检查他们。我选择导入org.hibernate.cfg包作为触发器。就是说,当一个bundle导入了这个包,它会自动得到net.sfl.cglib.*的包。如同所预期的,这个方法工作良好而且不需要从客户bundle得到任何特殊信息,不需要任何变化。然而,不幸的是存在一个竞态条件,而我们没有一个好的解决方案。如果扩展bundle在客户bundle之后启动,我们会有一个问题。解析的客户bundle会在扩展bundle有机会增加导入之前启动并创建一个会话工厂。作为另一种选择,附加的导入可以被持久性加入。在这种情况下,安装事件能够触发增加导入。这种情况下,竞态条件出现的窗口就很小了。这个问题被声明性方法的x-impliciwire阻止了。
那么什么是最佳解决方案?我确实不确定。我喜爱声明性方法的简洁性。当隐含的导入事先知道的情况下,它提供了一个干净而且非常简洁的方案。这个方法一个非常重要的优点是它没有竞态条件。然而,当这些导入是动态的,这个方法就不能用了。在运行时动态增加一个导入的程序性方法有所需要的灵活性,但是有竞态条件的问题。它需要部署者将扩展bundle的启动级别设置为开始级以确保它在客户bundle安装之前启动。
你怎么想?
Peter Kriens
|