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)方法中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
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) 方法中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
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; } |
Reference