读书人

Netbeans Platform: 登记与发现的机制

发布时间: 2012-12-18 12:43:41 作者: rapoo

Netbeans Platform: 注册与发现的机制

ex.printStackTrace();
}
}
// Fallback
if (t == null) {
t = new GenericAWTToolkit(); // 如果都不成功,返回一个通用的默认的实例
}
}

这种解决方案比以前有进步,没有烦人的setter方法和注册方法,不过它需要你在Java虚拟机运行时正确的设置属性。你不得不在程序启动时初始化。

我们看看Netbeans Platform如何做这件事情。首先,设计理念是,基于现有的标准,需要的时候作些增强,必须很好的和Netbeans运行时容器提供的动态模块环境相匹配。这种解决方案必须对任何接口和类都适合才行。并且它要支持监听器,允许任何人监听已经注册了的对象的任何变化。例如,监听一个模块的激活和失活。

Netbeans Platform如何做到这一点呢?让我们看看:Lookup!

MetaInf Service 元信息服务

JDK1.3的时候,Java提出了一个概念:服务提供者,Service Provider.这个概念介绍了一种完全是声明式的注册机制。实际上这种注册机制完全基于Java虚拟机的当前类路径,并没有什么其他的东西。这种注册机制使用Java类路径定义注册了的对象,非常容易使用。改变注册提供者,仅仅需要将提供者的jar包放在应用程序的类路径上就可以了。这样提供者能够迅速被查找到并且可以被其他代码访问。

背后的基本思想是每个希望提供某些接口实现的jar文件(Netbeans的术语就是模块),例如javax.xml.parser.DocumentBuilderFactory的实现,能够创建自己的接口实现,比如org.saxon.MyFactory. 然后通过在自己jar包中创建一个META-INF/services/javax.parser.DocumentBuilderFactory文件的方式暴露给系统,这被称为系统中的服务。这个文件中每一行包含一个实现类的名字。在这个例子中,这个文件中存在:org.saxon.MyFactory一行,文件名字就是接口,里面的内容的一行就是一个实现。

我们看一下如何运行的呢?当你使用DocumentBuilderFactory.newInstance()来创建一个实现实例的时候,系统会扫描所有的META-INF/services/javax.parser.DocumentBuilderFactory文件。如何扫描呢?通过ClassLoader.getResources("META-INF/services/javax.parser.DocumentBuilderFactory"). 系统将会读取文件的内容,通过默认构造器创建实例。究竟那一个实现会被创建呢?答案是第一个。

这种方式很好,也很流行了,在JDK1.6中,更是增加了一个辅助类java.util.ServiceLoader. 使用这个类,能更加容易的查找到所有注册的服务,不用手工编写“查找,读取,实例化”的代码。因此在JDK1.6中上述的任务简化为:

return ServiceLoader.load(DocumentBuilderFactory.class, someClassLoader).iterator().next();

OK, 让我们回到Netbeans上,Netbeans的解决方案基于上述技术。它被称为Lookup!

全局Lookup

org.openide.util.Lookup, 它和java.util.ServiceLoader一样是service provider模式。你可能要问,为什么你要单独搞出这么个东西?原因如下:

1. 如果你使用JDK1.6以前的版本(虽然不太可能),你可以使用Lookup作为替代

2. Lookup能够立刻在Netbeans运行时容器中使用。它知道如何发现系统中的模块,并且读取他们定义的服务。

3. Lookup支持监听器。客户代码可以设置监听器,监听Lookup内容的变化。这在动态环境中非常重要,模块可以在运行时激活或者失活,从而影响一系列注册服务的提供者。

4. Lookup可以扩展和替代。JDK1.6的java.util.ServiceLoader是最终类,并且是硬编码的,而Lookup是可扩展的类,允许各种实现,这在单元测试中尤其重要。甚至你可以编写一个增强版的Lookup, 让它不仅仅从META-INF/services中读取信息。

5. Lookup是通用的抽象层。每个classloader可以有一个JDK的ServiceLoader实例,而在Netbeans中可以有成千上万个相互独立的Lookup,每个可以代表一个单一的位置去发现和查询服务和接口。事实上,这正是Netbeans使用Lookup的方式:他代表每个对话,窗口元素,树中节点等的上下文。

我们重新看一下Validation的例子,在先前的例子中,Validation依靠维护一个Validator的Map进行注册和去注册。那么使用Lookup后,Validation根本不需要这个Map,代码如下:

package org.netbeans.examples.validate.api;
import org.openide.util.Lookup;
public final class Validations {
private Validations() {}

// 将Validator.class定制为一个Lookup模版 Lookup.Template,

// 通过Lookup获得Validator.class的模版查找到Lookup.Result结果

// 这个结果包含所有找到的Validator实例,遍历这些实例,找到相应的Validator实例来验证


private static Lookup.Result<Validator> validators =
Lookup.getDefault().lookup(
new Lookup.Template<Validator>(Validator.class)
);
// Or in 6.0 version just:
// Lookup.Result<Validator> validators =
// Lookup.getDefault().lookupResult(Validator.class);


public static void validate(String mimeType, InputStream is)
throws IOException {
for (Validator v : validators.allInstances()) {
if (v.supportsMimeType(mimeType)) {
v.validate(is);
return;
}
}
throw new IOException("No validator found for " + mimeType);
}
}

使用Lookup不仅省却了Validation的Map,而且不需要registerValidator等方法。全部依赖于Lookup.getDefault的实现和其注册方式。这样Validation类只需关注validate方法就可以了。

看看吧,现在如果我们想实现另一个验证器Manifest Validator. 我们不再需要知道ModuleInstall或者在模块初始化和结束化调用任何方法。我们仅仅需要声明ManifestValidator实现了Validator接口就可以了:

package org.netbeans.examples.manifestvalidator;
public final class ManifestValidator implements Validator {
public boolean supportsMimeType(String mimeType) {
return "text/x-manifest".equals(mimeType);
}
public void validate(InputStream is) throws IOException {
// Just try to read the file as manifest:
Manifest mf = new Manifest(is);
}
}

接下来怎么将ManifestValidator注册呢?很简单,在META-INF目录下创建META-INF/services/org.netbeans.examples.validate文件,告诉人们存在实现这个接口的实现,然后在这个文件中增加一行:org.netbeans.examples.manifestvalidator.ManifestValidator。告诉人们ManifestValidator实现了这个接口。万事OK了!记住啊,我们有个org.netbeans.examples.manifestvalidator包,这个包包括一个META-INF目录,这个目录下有一个以接口名字命名的文件,这个文件中有一行实现类的类名,注册完成了!

读书人网 >编程

热点推荐