OSGi Web启动的classloader机制(基于equinox3.5.2)

来源:互联网 发布:ubuntu 查找组 编辑:程序博客网 时间:2024/06/04 17:52

1. 使用服务器加载BridgeServlet的Classloader加载FrameworkLauncher类。

在equinox官方的BridgeServlet中,org.eclipse.equinox.servletbridge.BridgeServlet.init()方法中,首先调用该Servlet的加载classloader来加载,相关代码片段为:

 

然后调用framework.init(), framework.start()方法。

 

2. FramworkLauncher中,启动EclipseStarter类,启动OSGi容器,同时获得EclipseStarter中切换后的ThreadContextClassloader并缓存。

在framework.start()方法中,使用ChildFirstURLClassLoader加载org.eclipse.osgi_3.5.2.R35x_v20100114.jar中的org.eclipse.core.runtime.adaptor.EclipseStarter,然后缓存OSGi容器中使用的ThreadContextClassloader,代码片段如下:

 

通过反射调用了org.eclipse.core.runtime.adaptor.EclipseStarter两个方法:setInitialProperties和startup。setInitialProperties方法用于初始化FrameworkProperties类中的OSGi框架属性,该方法传入的属性map参数initialPropertyMap, 即是从/eclipse/launch.ini文件中获得的。frameworkContextClassLoader即为OSGi容器中真实的上ThreadContextClassloader,其实际的类型为:org.eclipse.core.runtime.internal.adaptor.ContextFinder。然后,在BridgeSerlevet中,反射调用OSGi内部类的时候,都需要将当前线程上下文加载器换成frameworkContextClassLoader,使得内部类的加载可以在OSGi上下文中执行。

 

关于launch.ini和eclipse/configuration/config.ini配置文件的关系,可以这样理解,launch.ini的属性优先级高于config.ini,而且launch.ini是被启动EclipseStarter的类负责解析后,在EclipseStarter启动时设置到org.eclipse.osgi.framework.internal.core.FrameworkProperties中。在org.eclipse.core.runtime.adaptor.EclipseStarter.startup(String[], Runnable)方法中,会获取config.ini文件属性,如果某个属性在FrameworkProperties为定义,则set进去。

 

对于Web OSGi server中,launch.ini和config.ini相关属性为:osgi.contextClassLoaderParent属性

 

#Let org.eclipse.core.runtime.internal.adaptor.ContextFinder's parent class loader as Framwork's classloader: That is the original class loader from server.

#fwk(the class loader of Osgi class Framework and EclipseStarter), ext(sysytem ext class loader), app(system classloader)

osgi.contextClassLoaderParent=fwk

 

 

为了解决OSGi Web Bundle中可能通过System.getProperty()和ServletContext中的属性,可能要加载server中的类的,此时会出现加载类失败的问题。这里可以设置为osgi.contextClassLoaderParent=fwk,org.eclipse.osgi.framework.internal.core.Framework.initializeContextFinder()方法中,会在初始化org.eclipse.core.runtime.internal.adaptor.ContextFinder类的时候,将之前的ChildFirstURLClassLoader(之前加载FrameworkLauncher类的org.eclipse.equinox.servletbridge.FrameworkLauncher.ChildFirstURLClassLoader)设置为其parent.

在ChildFirstURLClassLoader)类中,首先尝试从jars url中加载类,如果失败会从其parent(即为加载BridgeServlet的Classloader)加载。这样,Web bundle中可以访问到server中的类了。

 

org.eclipse.core.runtime.internal.adaptor.ContextFinder中,会对每个Bundle生成一个BundleClassloader,并且将自己作为每个BundleClassloader的parent。 但是,加载bundle中的类的时候,却都是直接用ContextFinder,由ContextFinder在loadClass方法中根据BundleContext.loadClass()的调用者,选择不同的BundleClassloader进行加载类。

如果ClassA为特定bundle内的类。要想获得此bundle的加载器,只能ClassA.class.getClassLoader(), 这样获得到的类型是 org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader。这是因为Bundle中Activator类始终是第一个被加载的,框架会调用Bundle.loadClass(name)加载,Acativator类的加载器就为bundle上下文中的DefualtClassLoader。后续的类都一次被Activator加载,理所当然ClassA的加载器也是DefaultClassLoader。


这里我们看看Bundle.loadClass(name)怎么实现。此处Bundle的实现类为BundleHost,此方法会转到其loadClass方法:

protected Class loadClass(String paramString, boolean paramBoolean)/*     */     throws ClassNotFoundException/*     */   {
// 检查类加载的权限是否满足/* 220 */     if (paramBoolean) {/*     */       try {/* 222 */         this.framework.checkAdminPermission(this, "class");/*     */       } catch (SecurityException localSecurityException) {/* 224 */         throw new ClassNotFoundException();/*     */       }/*     */     }
     
/* 227 */     BundleLoader localBundleLoader = checkLoader();/* 228 */     if (localBundleLoader == null)/* 229 */       throw new ClassNotFoundException(NLS.bind(Msg.BUNDLE_CNFE_NOT_RESOLVED, paramString, getBundleData().getLocation()));/*     */     try {/* 231 */       return localBundleLoader.loadClass(paramString);/*     */     }/*     */     catch (ClassNotFoundException localClassNotFoundException)/*     */     {/* 235 */       if ((!(localClassNotFoundException instanceof StatusException)) && ((this.bundledata.getStatus() & 0x2) != 0) && (!(testStateChanging(Thread.currentThread()))))/*     */         try/*     */         {/* 238 */           this.framework.secureAction.start(this, 1);/*     */         } catch (BundleException localBundleException) {/* 240 */           this.framework.adaptor.getFrameworkLog().log(new FrameworkLogEntry("org.eclipse.osgi", 2, 0, localBundleException.getMessage(), 0, localBundleException, null));/*     */         }/* 242 */       throw localClassNotFoundException;/*     */     }/*     */   }


这里首先检查类加载的权限有无。这里需要注意的时,在bundles启动器,环形依赖启动的时候,权限可能通不过。虽然equinox扩展点能一定程度上突破bundle不能环形依赖的限制,但是某些时候也会出问题。

localBundleLoader来自org.eclipse.osgi.framework.internal.core.BundleLoaderProxy getBundleLoader()方法,我们可以看到,如果当前bundle id=0,则代表system bundle,其他的都返回org.eclipse.osgi.framework.internal.core.BundleLoader类型。

synchronized BundleLoader getBundleLoader() {/*  47 */     if (this.loader != null)/*  48 */       return this.loader;/*  49 */     if (this.bundle.isResolved()) {/*     */       try {/*  51 */         if (this.bundle.getBundleId() == 0L) {/*  52 */           this.loader = new SystemBundleLoader(this.bundle, this); break label91:/*     */         }/*  54 */         this.loader = new BundleLoader(this.bundle, this);/*     */       } catch (BundleException localBundleException) {/*  56 */         this.bundle.framework.publishFrameworkEvent(2, this.bundle, localBundleException);/*  57 */         return null;/*     */       }/*     */     }/*  60 */     label91: return this.loader;/*     */   }

然后,在BundleLoader中,可是调度Bundle类加载。OSGi类加载的精华就在BundleLoader.findClassInternal方法中。

private Class findClassInternal(String paramString, boolean paramBoolean, ClassLoader paramClassLoader) throws ClassNotFoundException {/*  407 */     if (Debug.DEBUG_LOADER)/*  408 */       Debug.println("BundleLoader[" + this + "].loadBundleClass(" + paramString + ")");/*  409 */     String str = getPackageName(paramString);/*  410 */     int i = 0;/*      */ /*  412 */     if ((paramBoolean) && (paramClassLoader != null) && (isBootDelegationPackage(str)));/*      */     try/*      */     {/*  415 */       return paramClassLoader.loadClass(paramString);/*      */     }/*      */     catch (ClassNotFoundException localClassNotFoundException3) {/*  418 */       i = 1;/*      */ /*  420 */       Class localClass = null;/*      */       try {/*  422 */         localClass = (Class)searchHooks(paramString, 1);/*      */       } catch (ClassNotFoundException localClassNotFoundException1) {/*  424 */         throw localClassNotFoundException1;/*      */       }/*      */       catch (FileNotFoundException localFileNotFoundException1) {/*      */       }/*  428 */       if (localClass != null) {/*  429 */         return localClass;/*      */       }/*  431 */       PackageSource localPackageSource = findImportedSource(str, null);/*  432 */       if (localPackageSource != null)/*      */       {/*  434 */         localClass = localPackageSource.loadClass(paramString);/*  435 */         if (localClass != null)/*  436 */           return localClass;/*  437 */         throw new ClassNotFoundException(paramString);/*      */       }/*      */ /*  440 */       localPackageSource = findRequiredSource(str, null);/*  441 */       if (localPackageSource != null)/*      */       {/*  443 */         localClass = localPackageSource.loadClass(paramString);/*      */       }/*  445 */       if (localClass == null)/*  446 */         localClass = findLocalClass(paramString);/*  447 */       if (localClass != null) {/*  448 */         return localClass;/*      */       }/*  450 */       if (localPackageSource == null) {/*  451 */         localPackageSource = findDynamicSource(str);/*  452 */         if (localPackageSource != null) {/*  453 */           localClass = localPackageSource.loadClass(paramString);/*  454 */           if (localClass != null) {/*  455 */             return localClass;/*      */           }/*  457 */           throw new ClassNotFoundException(paramString);/*      */         }/*      */       }/*      */ /*  461 */       if (localClass == null)/*      */         try {/*  463 */           localClass = (Class)searchHooks(paramString, 2);/*      */         } catch (ClassNotFoundException localClassNotFoundException2) {/*  465 */           throw localClassNotFoundException2;/*      */         }/*      */         catch (FileNotFoundException localFileNotFoundException2)/*      */         {/*      */         }/*  470 */       if ((localClass == null) && (this.policy != null))/*  471 */         localClass = this.policy.doBuddyClassLoading(paramString);/*  472 */       if (localClass != null) {/*  473 */         return localClass;/*      */       }/*      */ /*  476 */       if ((paramClassLoader != null) && (i == 0) && ((((paramBoolean) && (this.bundle.framework.compatibiltyBootDelegation)) || (isRequestFromVM()))));/*      */       try/*      */       {/*  479 */         return paramClassLoader.loadClass(paramString);/*      */       }/*      */       catch (ClassNotFoundException localClassNotFoundException4)/*      */       {/*  483 */         throw new ClassNotFoundException(paramString); } }/*      */   }

这就能解释systembundle的fragement bundle发布的package,所有bundle都能访问到的原因。比如IBM Domino8中,systembundle的fargment bundle将Domino notes的api返回出来,Domino中所有的bundle都能访问NotesThread Notes Session等。WebOSGi应用也应用了此方法,将外部BridgeServlet等类发布给OSGi内部使用。也可以判断,当某个bundle和外部依赖的bundle包重名的时候,优先尝试加载外部依赖bundle中的类。在bundle classpath上设置的类,优先级当然会和定义的顺序一致。



所以,对于bundle中的某个类,Class.getClassloader(),得到的是该bundle专属的DefaultClassLoader。某种意义上,只要知道了bundle中的某个类,或者其classloader,里应外合,就能游刃有余的突破OSGi严格的类加载机制和bundle的export和依赖规则。