查找所有实现特定接口的类

44

我正在开发一个应用程序(Quartz scheduler)。在这个应用程序中,我们有一个工作类负责实际执行工作,我们需要在创建Quartz调度器的触发器时告诉/传递工作类的名称。

对于所有想使用API的人(除了我将作为API一部分提供的某些通用任务),我想提供一个扩展点。我的想法是创建一个(标记)接口,如果任何人想将他们的类声明为调度程序工作类,他们只需要实现该接口。

我不确定如何找到哪些类遵循协议(通过实现接口),以便我可以向想要在调度程序中安排触发器的用户显示它们。

我的要求不是在运行时加载类,而是向用户显示实现所需接口的类列表,以便用户可以选择类并将类名传递给调度程序。最终,Quartz调度程序将负责创建类的实例。

有人能建议我如何实现上述目标,或者是否有其他更好的方法来实现我所尝试做的事情吗?

编辑

我查看了ServiceLoader文档,似乎要实现服务,必须在META-INF文件夹中创建一个名为实现类的文件,这让我想到,如果我的API用户想要20个不同的实现,他们必须在文件中放置20个条目,这对于最终用户来说似乎是很多额外的工作,因为每个工作类将被创建以执行特定的工作,而可能会有数百个工作类。

如果我错误,请指正我。


2
另一种尝试是创建一个注释,然后搜索已注释的类。在这里https://dev59.com/WHVC5IYBdhLWcg3wixw0中介绍了如何实现它。 - polypiel
这个链接有帮助吗?链接 - titogeo
6个回答

45

你可以在这里找到答案。

我建议使用org.reflections。

你可以在这里查看它

Reflections reflections = new Reflections("com.mycompany");    
Set<Class<? extends MyInterface>> classes = reflections.getSubTypesOf(MyInterface.class);

谢谢您的意见,我正在查阅ServiceLocator文档。看起来对于我接口(服务)的每个实现,最终用户都必须在文件中添加条目以便加载。这是否意味着如果开发人员需要20个这样的实现,他/她需要在文件中列出所有这些实现名称,看起来对最终用户来说是很麻烦的工作。 - Umesh Awasthi
请阅读 https://meta.stackoverflow.com/tags/link-only-answers/info,并查看“[为什么在 SO 上提问时不上传代码图片?](https://meta.stackoverflow.com/a/285557/128421)”以及“[反对代码和/或错误的截图](https://meta.stackoverflow.com/questions/303812)”。 - the Tin Man
这段代码似乎在设计时硬编码了一个包名称。我们不知道开发人员可能会选择将他们的实现放在哪些包中。 - Ian Boyd

37

我有一个类似的需求,希望确保实现特定接口的所有类都是真正可序列化的。我创建了一个JavaClassFinder,它遍历类路径中的所有目录,并找到所有可分配给我关心的接口的类。以下是代码片段:

public <T> List<Class<? extends T>> findAllMatchingTypes(Class<T> toFind) {
    foundClasses = new ArrayList<Class<?>>();
    List<Class<? extends T>> returnedClasses = new ArrayList<Class<? extends T>>();
    this.toFind = toFind;
    walkClassPath();
    for (Class<?> clazz : foundClasses) {
        returnedClasses.add((Class<? extends T>) clazz);
    }
    return returnedClasses;
}

如果有帮助的话,我很乐意与您分享代码。唯一的缺点是它只能处理.class文件--我没有添加解压缩.jar并从中读取类文件的功能。(但添加这个功能不会是一个巨大的项目。)
更新:我检查了上述源代码,并发现它依赖于我们标准实用程序库中的许多辅助类。为了使它更容易,我打包了所有需要的代码,您可以从JavaClassFinder.zip下载。这将直接在Eclipse中设置,您可以获取所需代码的任何部分。
您将在项目中找到一个名为JavaClassFinderTest.java的JUnit3测试,其中显示了JavaClassFinder类的特性和用法。运行Junit测试所需的唯一外部依赖项是Junit。
此实用程序的基本用法:
    JavaClassFinder classFinder = new JavaClassFinder();
    List<Class<? extends MyTagInterface>> classes = classFinder.findAllMatchingTypes(MyTagInterface.class);

这将为您提供一个列表,其中包含可从“MyTagInterface.class”(例如)分配的类路径中的任何类。希望这可以帮助您。

"walkClassPath"方法缺失,请提供它好吗? - Yves Martin
@YvesMartin:Yves,我更新了我的答案,包括了下载原始代码的所有链接。可能比你想要的多,但它是一个完整的可工作版本。 - Sam Goldberg
3
非常感谢,你设计的实用工具非常出色。 - shashankaholic
1
@shashankaholic:谢谢 - 我很想知道你是否有任何用处。我们将其作为单元测试套件的一部分使用,以测试特定类型的所有子类是否真正可序列化。 (我发现仅实现Serializable并不能保证类的实例可以成功序列化。) - Sam Goldberg
@SamGoldberg:我使用了你的工具,在除了 Web 应用程序之外都完美运行。我看到了 JavaClassFinder 类中的这段代码 if (System.getProperties().containsKey(CUSTOM_CLASS_PATH_PROPERTY)) { // LOG.debug("getClassPathRoots(): using custom classpath property to search for classes"); classPath = System.getProperty(CUSTOM_CLASS_PATH_PROPERTY); } else { classPath = System.getProperty(JAVA_CLASS_PATH_PROPERTY); },似乎该代码获取的类路径是 Tomcat 中的 'bootstrap.jar'。我想知道是否有办法在 Web 应用程序中使用此实用程序。 - Umesh Awasthi
@user577691:我会看一下。自定义类路径属性只有在需要指定不同于应用程序运行时使用的类路径时才会使用。因此,如果您设置了该系统属性,它将覆盖应用程序类路径。另外,请问您能否澄清一下您的问题:您看到的问题是它只选择了Tomcat类路径而没有选择Web应用程序类路径吗? - Sam Goldberg

2

使用Java SPI机制可能是最好的(标准)方法,具体请参见Javadoc。不方便之处(也是一个很好的特性)在于它期望定义扩展的Jars将它们列在META-INF/services/your.fully.qualified.Interface中。

我能想到的另一种方式是遍历所有ClassLoader,希望你能够列出其中的文件,加载类文件,并查看它们是否实现了你的接口 - 这不是一件好事。


2
我认为,正如Alex Stybaev所提到的那样,org.reflections是一个合适的解决方案,您不需要在属性文件中引用这些类。
另一种方法(我会采取的方法)是使用Spring,因为我已经在我的应用程序中使用了Spring,因此不需要任何额外的依赖项。
您可以在此处找到使用Spring解决问题的提示(以及其他评论或答案中的替代方法): 在您的问题的评论中,heikkim和polypiel还链接到带有Spring解决方案的问题的答案:

0

在纯Java中这样做,最正确的答案已经被投票选出(来自Sam Goldberg 2012年4月10日)

然而,他的完整代码过于复杂。我使用了他的想法并将ClassFinder推到了那里: http://icedtea.classpath.org/hg/icedtea-web/file/0527ad4eb2dd/netx/net/sourceforge/jnlp/controlpanel/ClassFinder.java

注意-这将在独立应用程序中工作(或任何使用常规类路径和jar和dirs的应用程序),而不是在ServerContainer中。

如果您的应用程序在Web服务器上运行,则需要知道您的war / ear的位置并在那里搜索。或者您必须询问父类加载器它从哪里获取您的(和其他)源。

第二个注意事项-我正在过滤最终位置的类,以仅匹配netx和icedtea-web,因为我不希望搜索依赖项。因此,如果您需要包括rt.jar,请删除这些过滤器。或者如果您根本不需要它,请删除bootclasspath。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接