现在基于插件机制的软件越来越流行,这是一个好事情,给软件本身提供了很好的可扩展性,可以让第三方的开发人员基于提供的接口开发自己的插件。不管是哪个平台上,Windows 也好,Linux 也好,此类的软件是数不胜数,媒体播放器([foobar2000])、即时通讯软件([Gaim])、浏览器([Mozilla Firefox])、甚至于 Linux Kernel。在如果没有了可扩展性,恐怕很多人都不会去用这个软件,如果想要稍微改变一下软件的行为,或者增加更多的支持,就要去修改主程序的源代码,感觉上并不那么好。
不管叫插件(plugin)或者叫模块(module),还有扩展(extension),意思都类似。不过似乎稍微还是有点差别。扩展和模块比较类似。模块可能是更为独立,模块之间的相似性比较少,很可能两者之间根本没有关系,比如 Firefox 的扩展,可以做各种各样不同的事情。而插件呢,更可能是同一种类型的扩展,范围可能更窄一些,比如媒体播放器的解码器插件,针对不同的音频视频格式进行解码。
实现方式上基本上都相同,Windows 上使用动态链接库(dll),*NIX 上使用共享对象(so)。C 实现一般使用 dlopen、dlsym、dlclose 等函数操作从动态库中获取和运行具体的函数。Java 本身就具有动态装载的功能,并使用反射(Reflection)机制运行代码,已经在 Java 世界包括 JDK 本身广泛应用。
一般模块或者插件机制的实现,包括几个步骤:
- 发现插件。C 中一般是把所有的模块或者插件放在某个目录下,主程序起来以后,列举目录中的文件,再下一步检查是否是有效的插件。
- 装载插件。C 中使用
dlopen()打开一个具体的共享库,然后使用dlsym()检查一些特定的函数名,是不是程序所需要的,然后再做进一步的验证等。 - 运行插件中的代码。C 中通常都是函数指针,使用
dlsym()的结果就是需要的函数指针,就可以直接运行。
而 Java 由于本身的一些特点,某种程度上比 C 要简单一些。根据实现方法的不同,会稍有不同。
- 使用系统的 ClassLoader。举个 XML Parser 的例子,我们知道,在 JAXB(Java Architecture for XML Binding)中,使用
DocumentBuilderFactory来创建DocumentBuilder,那可能有很多种不同的 XML Parser 的实现,需要不同的DocumentBuilderFactory的实现。当然你可以直接指定到一个具体的实现,比如ab.cd.DocumentBuilderFactoryImpl,然后将包含这个类的 jar 包放在 CLASSPATH 中,直接 new 就可以了。但这不是我们所希望的,希望能够不改代码,可以直接换用别的解析器。Java 中提供了一种方法来实现这种功能。[JAR File Specification] 中指定了一个
META-INF/services/目录。可以在里面放置一些文件,文件名字为接口的名字,这里就是javax.xml.parsers.DocumentBuilderFactory,文件内容只有一行,就是包含在这个 jar 包中实现这个接口的子类,比如 [Jakarta Xerces] 的实现,在xerces.jar里面,内容为org.apache.xerces.jaxp.DocumentBuilderFactoryImpl。那么如何找到这个文件呢?我们先看看 JAXB 里面是怎么做的:
-
javax.xml.parsrs.DocumentBuilderFactory.newInstance() -
javax.xml.parsrs.FactoryFinder.find(String, String) -
javax.xml.parsrs.FactoryFinder.findJarServiceProvider(String) -
xxx.getResourceAsStream(ClassLoader, String)
-
- 自定义 ClassLoader。为一个应用程序编写的插件,一般情况下是不能直接用于别的程序的,也就是插件的特定性。那么就可以把这些插件放在一个目录里面,在应用程序启动的时候,把这个目录里面所有的包,使用我们自定义的 ClassLoader 读进来。包的格式可以仍然使用上一种方法,在
META-INF/services/里面以接口类名作为文件名,内容为实现类名。把所有这些都读入以后,我们就知道当前有多少个插件实现了某种接口,就可以直接使用。那有什么缺点呢?需要自己写个 ClassLoader,自己去找所有的文件,可能会稍微麻烦一点。而且这部分代码针对不同的程序结构是类似的,但具体的内容不同,可能存在重复代码的问题。
那么关键就是 xxx.getResourceAsStream,简单来说,就是在当前的 CLASSPATH 中,按顺序找相应的文件,这里就是 META-INF/services/javax.xml.parsers.DocumentBuilderFactory,然后将里面的内容(类名)读出来,使用反射机制来创建具体的实例。然后就可以使用了。这种机制称为“Service Provider API”。
但这样就有一个问题,这样必须将 jar 包放在 CLASSPATH 中,并且一个接口只能找到一个实现,在某些情况下是不够用的,比如媒体播放器,要支持多种解码器,现在只能支持一种,支持了这种,就支持不了那种,显然是不可用的。并且还可能需要动态支持,把相应的包仍进去就可以用,这仍然是不支持的。这就得要使用下面一种方法。
当然,这是最简单的情况,还有其他很多的问题需要考虑,比如安全问题、版本问题等等。
所以自然就有做出一个通用的架构这样一种需求。[Java Plug-in Framework] 就提供了这样一种架构,它使用 XML 作为插件描述文件,并提供了一个演示程序。简单扫了一眼以后,发现有个问题,本来我以为它就是一个库,应用程序只要调用它就可以了。但实际上不是这样,它把应用程序的入口也作为一个插件,完全通过它的框架来启动运行。感觉有点奇怪。不过看其特性,还是很丰富的。当然我也没有再仔细地去看。
今天在看 blog 的时候,发现另外一个东西,[JSR-277 Java Module System] [发布了] early draft,然后一些人给了评论,[OSGi Peter Kriens]、[robilad] 和 [Patrik Beno]。上面提到 JSR-277 将会在 JDK 7 中出现。
但另外一个 [JSR-291 Dynamic Component Support],看着就是上面的 [OSGi] 系统的原型,介绍中也提到了。不知道这两者是一个什么关系,不管从上面 Peter Kriens 的评论看来,他对这个很不爽,看来是竞争关系。呵呵。
这两份标准都是刚出的 early draft,我指示扫了一眼,没仔细看,有空再详细看一下。




More JSR-277 Early Draft Q & A
[More JSR-277 Q&A]
Post new comment