■ 概况:Discovery组件是发现模式(Discovery Pattern)的一个实现,它的目标是按照一种统一的方式定位和实例化类以及其他资源。 主要用于根据条件动态定义服务的提供者
■ 官方资源:主页,二进制,源代码。
■ 何时适用:当你想用最佳的算法在Java程序中查找Java接口的各种实现之时。
■ 应用实例:DiscoveryDemo.java,MyInterface.java,MyImpl1.java,MyImpl2.java,MyInterface。要求CLASSPATH中必须包含commons-discovery.jar和commons-logging.jar。
■ 说明:
Discovery的意思就是"发现",它试图用最佳的算法查找某个接口的所有已知的实现。在使用服务的场合,当我们想要查找某个服务的所有已知的提供者时,Discovery组件尤其有用。
考虑一下这种情形:我们为某个特别复杂的任务编写了一个接口,所有该接口的实现都用各不相同的方式来完成这个复杂任务,最终用户可以根据需要来选择完成任务的具体方式。那么,在这种情形下,最终用户应该用什么办法来找出接口的所有可用实现(即可能的完成任务的方式)呢?
上面描述的情形就是所谓的服务-服务提供者体系。服务的功能由接口描述,服务提供者则提供具体的实现。现在的问题是最终用户要用某种办法来寻找系统中已经安装了哪些服务提供者。在这种情形下,Discovery组件就很有用了,它不仅可以用来查找那些实现了特定接口的类,而且还可以用来查找资源,例如图片或其他文件等。在执行这些操作时,Discovery遵从Sun的服务提供者体系所定义的规则。
由于这个原因,使用Discovery组件确实带来许多方便。请读者参阅本文后面示例程序中的接口MyInterface.java和两个实现类MyImpl1.java、MyImple2.java,了解下面例子的细节。在使用Discovery的时候要提供MyInterface文件,把它放入META-INF/services目录,注意该文件的名字对应接口的完整限定名称(Fully Qualified Name),如果接口属于某个包,该文件的名字也必须相应地改变。
// …
// ① 创建一个类装入器的实例。
ClassLoaders loaders =
ClassLoaders.getAppLoaders(MyInterface.class, getClass(), false);
// …
// ② 用DiscoverClass的实例来查找实现类。
DiscoverClass discover = new DiscoverClass(loaders);
// …
// ③ 查找实现了指定接口的类:
Class implClass = discover.find(MyInterface.class);
System.err.println("Implementing Provider: " + implClass.getName());
运行上面的代码,就可以得到在MyInterface文件中注册的类。再次提醒,如果你的实现是封装在包里面的,在这里注册的名字也应该作相应地修改,如果该文件没有放在正确的位置,或者指定名字的实现类不能找到或实例化,程序将抛出DiscoverException,表示找不到符合条件的实现。下面是MyInterface文件内容的一个例子:MyImpl2 # Implementation 2。
当然,实现类的注册办法并非只有这么一种,否则的话Discovery的实用性就要大打折扣了!实际上,按照Discovery内部的类查找机制,按照这种方法注册的类将是Discovery最后找到的类。另一种常用的注册方法是通过系统属性或用户定义的属性来传递实现类的名字,例如,放弃META-INF/services目录下的文件,改为执行java -DMyInterface=MyImpl1 DiscoveryDemo命令来运行示例程序,这里的系统属性是接口的名字,值是该接口的提供者,运行的结果是完全一样的。
Discovery还可以用来创建服务提供者的(singleton)实例并调用其方法,语法如下:((MyInterface)discover.newInstance(MyInterface.class)).myMethod();。注意在这个例子中,我们并不知道到底哪一个服务提供者实现了myMethod,甚至我们根本不必关心这一点。具体的情形与运行这段代码的方式以及运行环境中已经注册了什么服务提供者有关,在不同的环境下运行,实际得到的服务提供者可能不同。