参考:
org.eclipse.osgi-3.6.0 源码
问题:
每个Bundle都有自己的类加载器,并且通过Import-Package和Export-Package来进行更广泛的协同类加载。但是如果我希望加入一些非OSGI的jar包作为lib,而不希望依赖它的全部bundle都修改一遍Import-Package,那要如何是好呢?
解决:
OSGI的bundle类加载,大致过程是,先委托给父类加载器,如果找不到,再委托导出了 本bundle需要导入的package 的bundle加载,再是Required Bundle,再是尝试动态导入选项,再是用自己的类加载器去加载。(这一过程请参考《深入理解OSGI》一书,或者看源码:org.eclipse.osgi.internal.loader.BundleLoader.findClass )
public Class findClass(String name) throws ClassNotFoundException {
return findClass(name, true);
}
Class findClass(String name, boolean checkParent) throws ClassNotFoundException {
ClassLoader parentCL = getParentClassLoader();
if (checkParent && parentCL != null && name.startsWith(JAVA_PACKAGE))
// 1) if startsWith "java." delegate to parent and terminate search
// we want to throw ClassNotFoundExceptions if a java.* class cannot be loaded from the parent.
return parentCL.loadClass(name);
try {
if (USE_GLOBAL_DEADLOCK_AVOIDANCE_LOCK)
lock(createClassLoader());
return findClassInternal(name, checkParent, parentCL);
} finally {
if (USE_GLOBAL_DEADLOCK_AVOIDANCE_LOCK)
unlock();
}
}
private Class findClassInternal(String name, boolean checkParent, ClassLoader parentCL) throws ClassNotFoundException {
if (Debug.DEBUG && 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) if part of the bootdelegation list then delegate to parent and continue of failure
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) search the imported packages
PackageSource source = findImportedSource(pkgName, null);
if (source != null) {
// 3) found import source terminate search at the source
result = source.loadClass(name);
if (result != null)
return result;
throw new ClassNotFoundException(name);
}
// 4) search the required bundles
source = findRequiredSource(pkgName, null);
if (source != null)
// 4) attempt to load from source but continue on failure
result = source.loadClass(name);
// 5) search the local bundle
if (result == null)
result = findLocalClass(name);
if (result != null)
return result;
// 6) attempt to find a dynamic import source; only do this if a required source was not found
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);
}
而osgi定义的类加载规范,bundle的类加载器都不同,但是bundle类加载器的父加载器却是同一个,并且是父加载器优先加载的,这就很符合公共lib的形式。
如果我们使用父加载器去加载作为lib的jar包,那么一切问题迎刃而解。
如何用父加载器,加载公共jar包呢?
要清楚这一点,先看看这一段源码(org.eclipse.osgi.baseadaptor.BaseAdaptor):
// System property used to set the parent classloader type (boot is the default)
private static final String PROP_PARENT_CLASSLOADER = "osgi.parentClassloader"; //$NON-NLS-1$
// A parent classloader type that specifies the application classloader
private static final String PARENT_CLASSLOADER_APP = "app"; //$NON-NLS-1$
// A parent classloader type that specifies the extension classlaoder
private static final String PARENT_CLASSLOADER_EXT = "ext"; //$NON-NLS-1$
// A parent classloader type that specifies the boot classlaoder
private static final String PARENT_CLASSLOADER_BOOT = "boot"; //$NON-NLS-1$
// A parent classloader type that specifies the framework classlaoder
private static final String PARENT_CLASSLOADER_FWK = "fwk"; //$NON-NLS-1$
// The BundleClassLoader parent to use when creating BundleClassLoaders.
private static ClassLoader bundleClassLoaderParent;
static {
// check property for specified parent
String type = FrameworkProperties.getProperty(BaseAdaptor.PROP_PARENT_CLASSLOADER, BaseAdaptor.PARENT_CLASSLOADER_BOOT);
if (BaseAdaptor.PARENT_CLASSLOADER_FWK.equalsIgnoreCase(type))
bundleClassLoaderParent = FrameworkAdaptor.class.getClassLoader();
else if (BaseAdaptor.PARENT_CLASSLOADER_APP.equalsIgnoreCase(type))
bundleClassLoaderParent = ClassLoader.getSystemClassLoader();
else if (BaseAdaptor.PARENT_CLASSLOADER_EXT.equalsIgnoreCase(type)) {
ClassLoader appCL = ClassLoader.getSystemClassLoader();
if (appCL != null)
bundleClassLoaderParent = appCL.getParent();
}
// default to boot classloader
if (bundleClassLoaderParent == null)
bundleClassLoaderParent = new ClassLoader(Object.class.getClassLoader()) {/* boot class loader*/};
}
app : 使用AppClassLoader 作为父加载器,加载器关系为 DefaultClassLoader -> AppClassLoader -> ExtClassLoader -> BootstrapClassLoader
ext :使用ExtClassLoader 作为父加载器,关系为 DefaultClassLoader -> ExtClassLoader -> BootstrapClassLoader
boot : 使用BootstrapClassLoader作为父加载器,关系为DefaultClassLoader -> ClassLoader -> BootstrapClassLoader
fwk : 使用FrameworkAdaptor类的加载器作为父加载器,这个就看具体实现了,如果没有自己写一个类加载器的话,那么FrameworkAdaptor的加载器会是AppClassLoader ,也就是说,等同于第一种情况。如果自己写了类加载器,那么就完全是自定义了。具体要怎么做暂时还没研究出来。
如果没有配置osgi.parentClassloader 这个属性,显然就是走的boot流程,也就是使用的BaseAdaptor里面一个临时的匿名的类加载器做父加载器。这样是没办法用这个父加载器做任何事情的。
最简单的实现公共jar的方法,就是设置为app方式,使用AppClassLoader加载公共jar包。下面讲下怎么做:
step 1 ~~
增加启动属性。
FrameworkProperties.setProperty("osgi.parentClassloader", "app");
至于这句什么意思怎么增加,请查看之前博客——从外部启动Equinox。
step 2 ~~
增加classpath。
在start.jar的MENIFEST.MF中,添加公共jar包作为classpath(注意最后一行要换行,多个依赖间要有空格):
Manifest-Version: 1.0
Main-Class: test.Main
Class-Path: org.eclipse.osgi-3.7.1.jar
lib/com.springsource.com.mysql.jdbc-5.1.6.jar
step 3 ~~
新建lib文件夹,把com.springsource.com.mysql.jdbc-5.1.6.jar这个jar包,从plugins文件夹,挪到lib下。