osgi的equinox实现中如何增加公共jar包

参考:

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*/};
	}

上面这段代码的意思就是,首先读取 osgi.parentClassloader 这个属性,如果设置成如下值,分别处理:

 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下。

你可能感兴趣的:(源码)