三层classloader
Bootstrap classLoader:采用native code实现,是JVM的一部分,主要加载JVM自身工作需要的类,如java.lang.*、java.uti.*等; 这些类位于$JAVA_HOME/jre/lib/rt.jar。Bootstrap ClassLoader不继承自ClassLoader,因为它不是一个普通的Java类,底层由C++编写,已嵌入到了JVM内核当中,当JVM启动后,Bootstrap ClassLoader也随着启动,负责加载完核心类库后,并构造Extension ClassLoader和App ClassLoader类加载器。
ExtClassLoader:扩展的class loader,加载位于$JAVA_HOME/jre/lib/ext目录下的扩展jar。
AppClassLoader:系统class loader,父类是ExtClassLoader,加载$CLASSPATH下的目录和jar;它负责加载应用程序主函数类。
应用场景
有时候我们需要在一个 Project 中运行多个不同版本的 jar 包,以应对不同集群的版本或其它的问题。如果这个时候选择在同一个项目中实现这样的功能,那么通常只能选择更低版本的 jar 包,因为它们通常是向下兼容的,但是这样也往往会失去新版本的一些特性或功能,所以我们需要以扩展的方式引入这些 jar 包,并通过隔离执行,来实现版本的强制对应。
注意事项
总的来说,实现隔离允许指定 jar 包,主要需要做到以下几点:
自定义 ClassLoader,使其 Parent = null,避免其使用系统自带的 ClassLoader 加载 Class。
在调用相应版本的方法前,更改当前线程的 ContextClassLoader,避免扩展包的依赖包通过Thread.currentThread().getContextClassLoader()获取到非自定义的 ClassLoader 进行类加载
通过反射获取 Method 时,如果参数为自定义的类型,一定要使用自定义的 ClassLoader 加载参数获取 Class,然后在获取 Method,同时参数也必须转化为使用自定义的 ClassLoade 加载的类型(不同 ClassLoader 加载的同一个类不相等)
OSGI Classloader
在OSGI中,每一个Bundle有一个单独的Classloader实例。更具体点,BundleWiringImpl中定义了一个BundleClassLoader,每当加载一个bundle时,框架创建一个BundleClassLoader实例负责该bundle相关class的加载工作。
BundleClassLoader的加载顺序如下:
如class在 java.* package中,委托Bootstrap Classloader处理;
如class定义在 OSGi 框架中启动委托列表(org.osgi.framework.bootdelegation)中,则将加载请求委托给Bootstrap Classloader处理;
如class在 Import-Package 定义的package中,则框架找到导出此package的 Bundle 的 Class Loader,交其处理 。
如class属于在 Require-Bundle 中定义的 Bundle,则框架找到导出此package的Bundle的ClassLoader,交其处理。
Bundle 搜索自己的类资源 ( 包括 Bundle-Classpath 里面定义的类路径和属于 Bundle 的 Fragment 的类资源);
若类在 DynamicImport-Package 中定义,则开始尝试在运行环境中寻找符合条件的 Bundle 。
Bundle之间隔离,但如果存在import关系又可以委托给相应export的classloader处理。实现上无非是维护了多个import bundle的Classloader,查找时调用其find方法实现。
需要注意的是:查找时优先查找Import-Package、Require-Bundle中的类,随后才是查找Bundle自己的类。这里又引入另外一个疑问,Embed-Dependency使用问题。
简单来说,如果一个Bunlde 需要使用protobuf-java.jar,有如下两种使用方式:
普通的dependency方式使用,如下图的Component C的使用方式。
Embed-dependency方式使用,如下图的ComponentA、ComponentB,此时将protobuf-java做为Bundle自身的一部分使用。
最后再来讲一下,为什么每个bundle需要分配单独的Classloader,解决什么问题。在我看来,最主要的原因有如下两个:
定制导出类。非osgi环境下,所有package中的java类都将被导出,无法限定哪些只能jar内使用,哪些是需要export出去的。存在各种误用,耦合使用情况。
多版本控制。非osgi环境下,一个jvm对于一个类只允许存在一个版本。osgi中每个bundle是独立开发演进的,可能出现同时存在多个版本。
# OSGi动态加载删除bundle
使用监听器 listeners
ServiceListener 和ServiceTracker 提供bundle和service的动态监听,ServiceTracker可以动态监听未来的bundle和service(OSGi Release 2提供的ServiceTracker ,一般推荐)
通过Declarative Service (OSGi DS,或者Spring Dynamic Module (DM))的方式(OSGi Release 4开始,重点推荐!)