CLR类加载简述与OSGi.NET插件平台类加载机制简述

一个插件平台除了需要考虑插件的结构、插件位置、插件类型空间、插件依赖、插件通讯、插件多版本支持、插件国际化等插件所需的基本要素之外,还需要考虑一个开发人员如何开发、调试和部署插件。本文简要描述了插件类型空间相关的知识——CLR Loader、CLR Loader VS Java ClassLoader和插件的类型空间及类加载机制实现。

1 CLR Loader

关于CLR加载器的详细描述可以查看《Essential .NET, Volume 1: The Common Language Runtime》,也可以下载我翻译的关于CLR部分的文档“CLR加载器 /Files/baihmpgy/CRL加载器.rar”。在这里我将描述一下书中没有说明的关于加载器的一些信息,这些信息包括:(1)何时触发类加载;(2)LoadFile的妙用及缺陷;(3)AppDomain.AssemblyResolve和AssemblyLoad事件。

在我目前认知情况下,我知道类加载发生在:A)CLR执行线程进入一个方法之前会触发JIT对方法进行编译并加载这个方法所需要的类型;B)调用Type.GetType等系统方法也会触发类型加载。为了让读者更好了解到类加载时机,我编写了一个Sample。该Sample的目录结构如下:
ClassLoaderTesting.exe
ClassLibrary1.dll
ClassLibrary2.dll
lib<directory>
---CL3<directory>
--------ClassLibrary.dll
你可以点击此处 /Files/baihmpgy/ClassLoaderTesting.rar下载Sample源码,在Program.cs的Main方法你可以看到类加载实验,AssemblyLoad和AssemblyResolve事件发生的时机。

LoadFile和LoadFrome的区别在于LoadFile每次加载一个程序集的时候,不管目前AppDomain是否已经加载了该程序集,它都会加载一次。此外,LoadFile不会加载目标程序集引用的程序集。它为我们提供了加载多版本程序集支持且可以做到按需加载一个程序集依赖的所有程序集。当然,目前而言,我所知的LoadFile的一个最大缺点,就是每次加载一个程序集后,它会Lock住程序集文件。这个缺点在支持插件调试时非常突出。

AssemblyResolve事件在CLR类加载器无法加载到所需类型时触发,它赋予了我们自定义类型加载的扩展,相反AssemblyLoad事件则发生在程序集被加载时。你可以通过调试ClassLoaderTesting这个项目来检查类型何时加载以及这两个事件何事触发。

2 CLR Loader Vs Java ClassLoader

这个问题比较古老了,已经有人比较过CLR Loader和Java ClassLoader。不过,要想公平公正的进行比较还真不容易,这就像公正比较Java和C#,J2EE和.NET一样。前一段时间,就有很多人在批Java。在比较CLR Loader和Java ClassLoader之前,先说几句废话。我先声明一下,我是一个C#程序员,也接触过Java。对Java类加载机制、Flex类加载机制有一定的实践。我体会到一些.NET平台相对于Java平台所不具备的东西,而这些差距除了.NET只支持Windows和Linux之外,还有就是.NET缺少的开放规范和缺少灵活的类加载机制。众所周知,Java有一堆的规范,比如JDBC、JMS、JMX、JNDI、JTX等等,当然,也有基于这些规范的各种Providers,基于Java平台我们可以有很多挑选的余地,并且可以从这些高端的开放规范中学习到很多的知识,.NET平台就很少有开放规范了,如果你非得说C#语言规范的话,那也算,:)。那些标榜“C#比Java绝对好”结论相关的文章我一般都懒得回复,因为这没有任何意义,我只想根据实际需要选择更好的,而不是片面的排斥。要知道,现在用Java的程序员可是C#的几倍,而且Java已经被认为是云计算的标准开发语言(插一句,我不幸的是用C#构建了一个SaaS引擎,深受客户的质疑),存在即合理。

言归正传,我将在http://blog.joycode.com/junfeng/archive/2004/04/10/18901.joy这篇文章之上谈一下二者。在Java,类是通过ClassLoader和类型全程来标识的,这意味着一个ClassLoader不能加载重复的类型,但是两个ClassLoader可以加载同一个名称的类型。不过这时候,被加载的这两个类型创建的实例就是隶属于不同类型了。据我说知,Java类加载触发有两种情况:A)JVM执行过程中;B)Class.forName。JVM在执行字节码的过程中,碰到一个未知类型后,会从当前线程获取对应的ClassLoader,然后使用ClassLoader加载类型。ClassLoader加载类型采用一种层级方式,它首先先调用父类加载器进行加载,如果加载不到才使用当前线程的ClassLoader进行加载。我们可以通过重写ClassLoader对JVM类型加载进行扩展。著名的Eclipse的OSGi内核——Equniox就是通过重写ClassLoader自定义了一个插件类型空间的类型加载器。Class.forName则是显式调用当前线程类加载器加载类型的方法了。很多Java组件框架都是通过扩展ClassLoader来实现模块化技术的,比如JBoss和NetBeans。

CLR加载器概念,我们接触的机会很少,相反,我们对Assembly.Load/LoadFrom/LoadFile、Type.GetType倒是经常碰到了,你可以通过《Essential .NET, Volume 1: The Common Language Runtime》该书深入理解CLR加载器。和Java ClassLoader相比,它的缺点主要有:(1)扩展性差;(2)加载的类型卸载的唯一方式是随着AppDomain一起被卸载。扩展性差表现在,实现CLR自定义类型加载只能通过AppDomain.AssemblyResolve来实现(还有一种方法,就是可以通过.config文件来配置类型加载,但这种方法是静态的,我没有把它当成一种方法),Assembly.Load/LoadFrom/LoadFile这些方法并不能实现对CLR加载器进行扩展,它们只是一些补充,原因很简单,在执行“Class3 cls = new Class3();”时,Class3只能是由CLR来加载的。第二个缺点,很明显,一个类型一旦被加载了,它是无法卸载的,因为在C#,我们在不使用低性能和跨进程调用的AppDomain下是无法实现一个支持动态更新的插件平台。

我的结论:Java ClassLoader简单优雅、功能强大、非常灵活,但对多版本不提供支持;CLR Loader非常简单、支持多版本、但不太灵活且扩展性稍差。

3 OSGi.NET BundleLoader

OSGi.NET是OSGi规范移植到.NET的实现。OSGi是一个基于Java的动态模块化系统的规范。它提供了模块化与插件化、面向服务、安全与隔离和模块扩展支持的功能。由于OSGi具有规范的模块化与插件化定义,且已经经过了Eclipse IDE考验,我们便制定了基于C#的OSGi.NET开放服务规范并基于C#开发实现。

在设计OSGi.NET过程中,模块层花费了大量的设计和开发的时间。我从易用性、类型空间独立性、类加载性能等方面考了,设计了一个几乎和OSGi模块类加载器一样优雅的插件类型加载机制。它类似OSGi实现了一个插件的独立类型空间,在OSGi.NET中,一个插件能加载的类型由插件与子插件本地程序集、插件与子插件依赖程序集、插件动态依赖程序集组成,通俗的讲,一个插件只能从本地和依赖的程序集加载所需的类型。这种方式使得多版本程序集支持也变得可能。OSGi.NET插件类型加载由BundleLoader即插件类加载器来实现,其实现原理和OSGi类似。

不过,为了实现每一个插件独立的类型空间,我们需要对CLR加载器进行扩展,其方式如下:
 1  AppDomain.CurrentDomain.AssemblyResolve  +=   new  ResolveEventHandler(DomainAssemblyResolve);
 2 
 3  Assembly DomainAssemblyResolve( object  sender, ResolveEventArgs args)
 4  {
 5       string  resolvingAsmName  =  args.Name;
 6      AssemblyName name  =   new  AssemblyName(resolvingAsmName);  // 获取程序集全名。
 7      Assembly asm  =  SearchAssemblyFromAllBundles(name);  // 从所有Bundle的程序集中查找匹配的程序集并加载。
 8 
 9       return  asm;
10  }

 


到目前为止我对OSGi.NET的插件化支持还算满意,但限于个人对Java ClassLoader、CLR Loader和OSGi等精深知识理解还不够深入,如有错误,欢迎指正。

你可能感兴趣的:(.net)