今天闲着没事儿,研究了一把Apache Commons Discovery包,发现还不错,跟大家分享一下。
Discovery组件被用以查找可插拔接口的实现实例,它提供了一种通用的实例化这些实现的方式,而且可以管理单例(工厂)的生命周期。本质上来讲,就是定位那些实现了给定Java接口的类,并实例化。除此之外,Discovery还可以用以在给定的classpath中查找并加载资源文件。
Discovery组件在查找所有的实现类的时候需要预先将允许被查找的实现类配置到默认的配置文件中,默认的配置文件为:
/META-INF/services/<YOUR Interface whole name including pkg name>, Discovery将依次加载该文件中配置的允许加载的实现类。
下面将举例说明:
首先定义一个Interface:Action
package com.javaeye.terrencexu.discovery; public interface Action { public String getName(); }
然后在不同的包里分别实现Action接口,如下(请注意包名)
package com.javaeye.terrencexu.discovery.impl; import com.javaeye.terrencexu.discovery.Action; public class CreateAction implements Action { @Override public String getName() { return "Create Action"; } }
package com.javaeye.terrencexu.discovery.impl; import com.javaeye.terrencexu.discovery.Action; public class DeleteAction implements Action { @Override public String getName() { return "Delete Action"; } }
package com.javaeye.terrencexu.discovery.impl2; import com.javaeye.terrencexu.discovery.Action; public class AddAction implements Action { @Override public String getName() { return "Add Action"; } }
package com.javaeye.terrencexu.discovery.impl2; import com.javaeye.terrencexu.discovery.Action; public class RemoveAction implements Action { @Override public String getName() { return "Remove Action"; } }
接下来将定义配置文件,按序定义允许被加载的实现类,该文件默认存在位置为/META-INF/services/,文件名为com.javaeye.terrencexu.discovery.Action,文件内容如下:
# Display in order com.javaeye.terrencexu.discovery.impl.CreateAction com.javaeye.terrencexu.discovery.impl.DeleteAction com.javaeye.terrencexu.discovery.impl2.AddAction
这样所有的准备材料就都已经齐全了,接下来可以验证一把了,如下:
/** * CreateAction/DeleteAction/AddAction have been defined in /META-INF/services/com.javaeye.terrencexu.discovery.Action * * And the order is CreateAction > DeleteAction > AddAction */ @SuppressWarnings("unchecked") public void testGetAllProviders() { String[] expectedResults = new String[] {"Create Action", "Delete Action", "Add Action"}; Enumeration<Action> enu = Service.providers(Action.class); int count = 0; while (enu.hasMoreElements()) { Action action = enu.nextElement(); assertTrue("The action name should be \"" + expectedResults[count] + "\", but actually is \"" + action.getName() + "\"", action.getName().equals(expectedResults[count])); count ++; } assertEquals(count, expectedResults.length); }
可以发现,因为RemoveAction没有被配置到service文件中,所以将不会被加载,另外一点儿就是,配置文件中的定义顺序即加载顺序。
除此之外,Discovery提供了singleton模式加载唯一实现,并且该实现将被缓存在cache中,除非通过显示的调用release()方法释放缓存,否则所有之后的调用,都将返回初次调用加载的Action。
public void testFindCreateAction() { try { // Load provider com.javaeye.terrencexu.discovery.impl.CreateAction Action createAction = (Action) DiscoverSingleton.find(Action.class, CreateAction.class.getName()); assertEquals("Create Action", createAction.getName()); } finally { DiscoverSingleton.release(); } }
还有一点需要说明的是,如果定义了默认的service文件,无论通过singleton模式加载的实现类有没有被配置在service文件中,都将默认加载配置中文中的第一个Action,比如上文中的CreateAction。
public void testFindDeleteActionInConfig() { try { // Load provider com.javaeye.terrencexu.discovery.impl.CreateAction Action deleteAction = (Action) DiscoverSingleton.find(Action.class, DeleteAction.class.getName()); // As the default configuration file defines the CreateAction as the first element, so you will always get the CreateAction as singleton. assertEquals("Create Action", deleteAction.getName()); } finally { DiscoverSingleton.release(); } }
那么如果必须使用service文件,又想通过singleton模式加载某特定的实现类该怎么办呢?可以通过传递Properties到DiscoverSingleton中去改变它的行为,如下:
public void testFindDeleteActionWithProperty() { try { Properties props = new Properties(); props.setProperty(Action.class.getName(), DeleteAction.class.getName()); // Load provider com.javaeye.terrencexu.discovery.impl.CreateAction Action deleteAction = (Action) DiscoverSingleton.find(Action.class, props); assertEquals("Delete Action", deleteAction.getName()); } finally { DiscoverSingleton.release(); } }
除了加载类之外,很多情况下我们还想加载资源文件,比如在你的classpath下有一个配置文件为/conf/testResource,下面我们通过Discovery去加载该资源文件:
public void testFindResources() { ClassLoaders loaders = new ClassLoaders(); ClassLoader cl = getClass().getClassLoader(); if(cl != null) { loaders.put(getClass().getClassLoader(), true); } else { loaders.put(JDKHooks.getJDKHooks().getSystemClassLoader(), true); } String name = "conf/testResource"; DiscoverResources discovery = new DiscoverResources(loaders); ResourceIterator iter = discovery.findResources(name); while(iter.hasNext()) { Resource resource = iter.nextResource(); URL url = resource.getResource(); System.out.println(url); } }
Discovery还有其他一些功能这里就不在详细的一一赘述了,可以参考http://commons.apache.org/discovery/index.html进一步详细了解。
下图是我的例子的目录结构,仅供参考:
然后附件中有source code,仅供参考。