osgi容器中有三种classloader:各个bundle都有自己的classLoader,osgi容器也有一个框架级的classloader,这些classloader也不同于启动osgi容器的classloader(一般是app classloader)。如果需要加载一个类,那么就需要在这三个classloader之间挑出一个合适的classloader来加载类。本文将根据equinox和felix源码肤浅的描述一下osgi类加载顺序。
osgi规范中类加载顺序的定义
首先osgi类加载顺序不是开发osgi框架的人定的,而是osgi规范定的,但是每个框架的实现可能有所不同(规范详见osgi R4.3中的第3.9.4节 《Overall Search Order》):
在类和资源加载过程中,框架必须遵循以下规则。当请求一个bundle类加载器进行类加载或者资源的查找,查找必须按照以下顺序执行:
- 如果类或者资源是在包java.*中,那么交由父级类加载器代理完成,否则,搜索过程进入第二步。如果父类级类加载器加载失败,那么查找过程结束,加载失败。
- 如果类或者资源在启动代理序列(org.osgi.framework.bootdelegation)中定义,那么交由父级代理完成,此时的父级代理有启动参数org.osgi.framework.bundle.parent指定,默认是引导类加载器(bootstrap class loader),如果找到了类或者资源,那么查找过程结束。
- 如果类或者资源所在的包是在Import-Package中指定的,或者是在此之前通过动态导入加载的了,那么将请求转发到导出bundle的类加载器,否则搜索继续进行下一步;如果该包在启动参数org.osgi.framework.system.packages.extra中,则将请求转发给osgi容器外部的类加载器(通常是系统类加载器)。如果将请求交由导出类加载器代理,而类或者资源又没有找到,那么查找过程中止,同时请求失败。
- 如果包中类或者和资源所在的包由其他bundle通过是使用Require-Bundle从一个或多个其他bundle进行导入的了,那么请求交由其他那些bundle的类加载器完成,按照根据在bundle的manifest中指定的顺序进行查找进行查找。如果没有找到类或者资源,搜索继续进行。
- 使用bundle本身的内部bundle类路径查找完毕之后,。如果类或者资源还没有找到,搜索继续到下一步。
- 查找每一个附加的fragment的内部类路径,fragment的查找根据bundle ID顺序升序查找。如果没有找到类或者资源的,查找过程继续下一步。
- 如果包中类或者资源所在的包由bundle导出,或者包由bundle导入(使用Import-Package或者Require-Bundle),查找结束,即类或者资源没有找到。
- 否则,如果类或者资源所在的包是通过使用DynamicImport-Package进行导入,那么试图进行包的动态导入。导出者exporter必须符合包约束。如果找到了合适的导出者exporter,然后建立连接,以后的包导入就可以通过步骤三进行。如果连接建立失败,那么请求失败。
- 如果动态导入建立了,请求交由导出bundle的类加载器代理。如果代理查找失败,那么查找过程中止,请求失败。
下面的非标准流程图展示了查找的过程:
从规范可见,类加载的优先级顺序基本按照如下的原则:父容器classloader(通常是app classloader) –> 其他bundle的classloader –> 当前bundle的classloader –> 动态导入的包所在bundle的classloader。这个原则既可以使相同的类(包名也相同)尽可能只被加载一次,减少虚拟机perm区大小,也正因为如此,不同bundle中的相同的类,委托给同一个classloader加载,才能做到他们的对象和引用可以相互转换。(要知道一个类如果由不同的classloader加载后,其中一个classloader加载的类的对象是不能赋值给另一个classloader加载的类的引用的。)
osgi类加载顺序的源码解析
目前最流行的osgi框架实现有两个:equinox和felix,下面分别列出equinox和felix在类加载顺序上的实现代码,如果大家在开发过程中发现一些类加载方面的奇怪现象,就可以通过找到如下的代码位置进行调试,从而跟踪出问题的根源,尤其是需要osgi容器内外共享类的时候。
equinox的osgi类加载顺序实现代码
在org.eclipse.osgi.internal.loader.BundleLoader.java的findClass(String name, boolean checkParent)和findClassInternal(String name, boolean checkParent, ClassLoader parentCL)方法中:
Class<!--?--> findClass(String name, boolean checkParent) throws ClassNotFoundException {
ClassLoader parentCL = getParentClassLoader();
if (checkParent && parentCL != null && name.startsWith(JAVA_PACKAGE))
// 1) 如果类的包名以"java."开头,则委托给父容器加载器加载(默认是bootstrap classloader)
return parentCL.loadClass(name);
return findClassInternal(name, checkParent, parentCL);
}
private Class<!--?--> findClassInternal(String name, boolean checkParent, ClassLoader parentCL) throws ClassNotFoundException {
if (Debug.DEBUG_LOADER)
Debug.println("BundleLoader[" + this + "].loadBundleClass(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
String pkgName = getPackageName(name);
boolean bootDelegation = false;
// follow the OSGi delegation model
if (checkParent && parentCL != null && bundle.getFramework().isBootDelegationPackage(pkgName))
// 2) 如果该类在父容器类加载器委托清单中(即osgi容器启动参数org.osgi.framework.bootdelegation的值中)
// 则委托给父容器类加载器加载(默认是bootstrap classloader)。
try {
return parentCL.loadClass(name);
} catch (ClassNotFoundException cnfe) {
// we want to continue
bootDelegation = true;
}
Class<!--?--> result = null;
try {
result = (Class<!--?-->) searchHooks(name, PRE_CLASS);
} catch (ClassNotFoundException e) {
throw e;
} catch (FileNotFoundException e) {
// will not happen
}
if (result != null)
return result;
// 3) 在Import-Package中查找该类来自哪个bundle
PackageSource source = findImportedSource(pkgName, null);
if (source != null) {
// 3) 由查找到的bundle加载这个类
result = source.loadClass(name);
if (result != null)
return result;
throw new ClassNotFoundException(name);
}
// 4) 在Require-Bundle中查找哪一个bundle中有这个类
source = findRequiredSource(pkgName, null);
if (source != null)
// 4) attempt to load from source but continue on failure
result = source.loadClass(name);
// 5) 在当前的bundle中查找和加载该类
if (result == null)
result = findLocalClass(name);
if (result != null)
return result;
// 6) 在DynamicImport-Package中查找并加载
if (source == null) {
source = findDynamicSource(pkgName);
if (source != null) {
result = source.loadClass(name);
if (result != null)
return result;
// must throw CNFE if dynamic import source does not have the class
throw new ClassNotFoundException(name);
}
}
if (result == null)
try {
result = (Class<!--?-->) searchHooks(name, POST_CLASS);
} catch (ClassNotFoundException e) {
throw e;
} catch (FileNotFoundException e) {
// will not happen
}
// do buddy policy loading
if (result == null && policy != null)
result = policy.doBuddyClassLoading(name);
if (result != null)
return result;
// hack to support backwards compatibiility for bootdelegation
// or last resort; do class context trick to work around VM bugs
if (parentCL != null && !bootDelegation && ((checkParent && bundle.getFramework().compatibiltyBootDelegation) || isRequestFromVM()))
// we don't need to continue if a CNFE is thrown here.
try {
return parentCL.loadClass(name);
} catch (ClassNotFoundException e) {
// we want to generate our own exception below
}
throw new ClassNotFoundException(name);
}
felix的osgi类加载顺序实现代码
在org.apache.felix.framework.BundleWiringImpl.java类的findClassOrResourceByDelegation(String name, boolean isClass) 方法中:
private Object findClassOrResourceByDelegation(String name, boolean isClass)
throws ClassNotFoundException, ResourceNotFoundException
{
Object result = null;
Set requestSet = (Set) m_cycleCheck.get();
if (requestSet == null)
{
requestSet = new HashSet();
m_cycleCheck.set(requestSet);
}
if (requestSet.add(name))
{
try
{
// Get the package of the target class/resource.
String pkgName = (isClass)
? Util.getClassPackage(name)
: Util.getResourcePackage(name);
// 如果该类在父容器类加载器委托清单中(即osgi容器启动参数org.osgi.framework.bootdelegation的值中)
if (shouldBootDelegate(pkgName))
{
try
{
// 根据osgi容器启动参数org.osgi.framework.bundle.parent的值来获得适当的父类加载器
// 默认是bootstrap classloader
ClassLoader bdcl = getBootDelegationClassLoader();
result = (isClass)
? (Object) bdcl.loadClass(name)
: (Object) bdcl.getResource(name);
// If this is a java.* package, then always terminate the
// search; otherwise, continue to look locally if not found.
if (pkgName.startsWith("java.") || (result != null))
{
return result;
}
}
catch (ClassNotFoundException ex)
{
// If this is a java.* package, then always terminate the
// search; otherwise, continue to look locally if not found.
if (pkgName.startsWith("java."))
{
throw ex;
}
}
}
// 在Import-Package和Require-Bundle中查找是否有该类,有则委托给相应的加载器加载,详见下面searchImports方法的代码
result = searchImports(pkgName, name, isClass);
// 如果还没找到,就在当前bundle的classpath下查找是否有该类,有则加载.
if (result == null)
{
if (isClass)
{
ClassLoader cl = getClassLoaderInternal();
if (cl == null)
{
throw new ClassNotFoundException(
"Unable to load class '"
+ name
+ "' because the bundle wiring for "
+ m_revision.getSymbolicName()
+ " is no longer valid.");
}
result = (Object) ((BundleClassLoader) cl).findClass(name);
}
else
{
result = (Object) m_revision.getResourceLocal(name);
}
// 如果还是没找到,则在DynamicImport-Package中查找和加载
if (result == null)
{
result = searchDynamicImports(pkgName, name, isClass);
}
}
}
finally
{
requestSet.remove(name);
}
}
else
{
// If a cycle is detected, we should return null to break the
// cycle. This should only ever be return to internal class
// loading code and not to the actual instigator of the class load.
return null;
}
if (result == null)
{
if (isClass)
{
throw new ClassNotFoundException(
name + " not found by " + this.getBundle());
}
else
{
throw new ResourceNotFoundException(
name + " not found by " + this.getBundle());
}
}
return result;
}
// 在Import-Package和Require-Bundle中查找是否有该类,有则委托给相应的加载器加载
private Object searchImports(String pkgName, String name, boolean isClass)
throws ClassNotFoundException, ResourceNotFoundException
{
// 从Import-Package中查找和加载.
BundleRevision provider = m_importedPkgs.get(pkgName);
if (provider != null)
{
// 如果pkgName配置在启动参数org.osgi.framework.system.packages.extra 的值中,
// 则由启动osgi容器的类加载器来加载这个类或者资源,(一般情况是系统类加载器加载)。
// 其classloader来自ExtensionManagerWiring的getClassByDelegation 方法,
// 其实质是ExtensionManagerWiring的getClass().getClassLoader().loadClass(name) 来加载的,
// 而ExtensionManagerWiring类是由启动osgi容器的类加载器加载;
// 如果pkgName未配置在启动参数org.osgi.framework.system.packages.extra的清单中,
// 则用相应的bundle的classloader来加载
Object result = (isClass)
? (Object) ((BundleWiringImpl) provider.getWiring()).getClassByDelegation(name)
: (Object) ((BundleWiringImpl) provider.getWiring()).getResourceByDelegation(name);
if (result != null)
{
return result;
}
// If no class or resource was found, then we must throw an exception
// since the provider of this package did not contain the
// requested class and imported packages are atomic.
if (isClass)
{
throw new ClassNotFoundException(name);
}
throw new ResourceNotFoundException(name);
}
// 从Require-Bundle中查找和加载.
List providers = m_requiredPkgs.get(pkgName);
if (providers != null)
{
for (BundleRevision p : providers)
{
// If we find the class or resource, then return it.
try
{
Object result = (isClass)
? (Object) ((BundleWiringImpl) p.getWiring()).getClassByDelegation(name)
: (Object) ((BundleWiringImpl) p.getWiring()).getResourceByDelegation(name);
if (result != null)
{
return result;
}
}
catch (ClassNotFoundException ex)
{
// Since required packages can be split, don't throw an
// exception here if it is not found. Instead, we'll just
// continue searching other required bundles and the
// revision's local content.
}
}
}
return null;
}