OSGI Bundle和Web容器加载类冲突问题的分析和解决

来源:互联网 发布:python运维框架 编辑:程序博客网 时间:2024/06/06 01:59

    一、预备知识:

         1)web应用的类加载器

         web容器像JBoss、Tomcat的类加载器是用户自定义的类加载器,这个类加载器会默认加载WEB-INF下的classes、lib目录下的所有类或者jar包。这也就为什么我们将jar包放在web容器的该目录下,web容器就会自动加载这些类和jar包的原因。
         2)为什么需要线程上下文类加载器?
         根本原因是弥补JVM双亲代理模式的先天缺陷。双亲代理模型查找类的顺序是从下往上找,先让自己的父加载器找,找不到才自己找,依次是:应用类加载器-->扩展类加载器-->boot类加载器。其缺点是无法从上往下找,而在JVM中存在大量需要从上往下找类的情况, 比如JMS、JDBC、JNI等,它们都存在官方规范,JVM也将其整合到JVM中去了。既然是规范,当然JVM就只能提供接口,而具体接口的实现必须由具体的实现方来提供,这些接口都是boot类加载器来加载,它已经是最顶端的加载器了。如果要加载JDBC等接口的实现类时,无父加载器可用,自己也找不到,怎么办?我们姑且用JDBC来举例,JDBC是一个标准,JVM中只提供了JDBC的接口,具体的数据库驱动比如Oracle的ojdbc6.jar、MySQL的mysql-connector-java-5.0.4-bin.jar都实现了这些接口(而且这也是必然的模式,因为不同的数据库驱动内部实现当然不同,JDBC接口的存在恰到好处地隔离了这些驱动之间的不同,多说一句,这也是接口存在的理由)。当前线程类加载器默认是当前应用类加载器,在web容器中(tomcat、JBoss)中,也就是web容器的类加载器。再结合第1)点,这样这个上下文类加载器就构成了JVM和web容器的classes中的类、lib下的jar包之间的桥梁,JVM就可以通过这个当前线程上下文类加载器从上往下加载类了。所以,我们的Oracle、MySQL的驱动放在lib下,JVM是可以找的到的。

         3)在JVM中,关于类唯一性判断格式是:Class全名+Classloader的Id名。因此即使类名一样,类加载器不同也不是同一个类型。

     二、当前问题的分析和解决

             理论上,我们Bundle不应该和Web容器等这些运行环境之间出现依赖。这样才能真正做到解耦、才能实现动态可拔插、可替换等OSGI特性。在我们的环境中,最外层运行的是web容器,然后是Felix。由于我打的Bundle中大量使用了第三方jar包,通过仔细跟源码我们发现在这里第三方jar包中大量使用了Thread.currentThread().getContextClassLoader()这样的语句获取类加载器,根据上边的知识我们知道我们当前线程上下文类加载器就是web容器的类加载器,而恰恰这些jar包中做了以下的动作:分别用这当前线程上下文类加载器、当前Bundle的类加载器去加载jar包,而这个时候我们的Bundle中存在和web容器lib下相同的jar包。这样在第三方程序中做类似类型强转等动作的时候就会报错。原因,我们在上边第3)点中已经说到了。

             分析清楚了,解决起来就非常简单。既然我们在我们当前的Bundle并不想被web容器干扰,最简单的办法就是,使用Thread.currentThread().setContextClassLoader(null)语句将其清掉。当然了,这可能也会带来其他问题,这个就需要你去适当解决了,办法总是有的。

      结论:当前线程上下文类加载器虽然在某种程度上给我们提供了方便,但是同时也成为我们Bundle和其运行环境耦合的始作俑者,这个和OSGI的设计思想是相悖的,我们使用的时候一定要仔细斟酌。

原创粉丝点击