如何以Java编程方式获取接口的所有实现的列表?[关闭]


Answers:


56

我已经搜索了一段时间,似乎有不同的方法,这里是一个摘要:

  1. 如果您不介意添加依赖项,则反射库非常受欢迎。它看起来像这样:

    Reflections reflections = new Reflections("firstdeveloper.examples.reflections");
    Set<Class<? extends Pet>> classes = reflections.getSubTypesOf(Pet.class);
    
  2. ServiceLoader(根据埃里克森的回答),看起来像这样:

    ServiceLoader<Pet> loader = ServiceLoader.load(Pet.class);
    for (Pet implClass : loader) {
        System.out.println(implClass.getClass().getSimpleName()); // prints Dog, Cat
    }
    

    请注意,要使其正常工作,您需要定义Pet为ServiceProviderInterface(SPI)并声明其实现。你这样做,通过创建一个文件resources/META-INF/services的名称examples.reflections.Pet和声明的所有实现Pet

    examples.reflections.Dog
    examples.reflections.Cat
    
  3. 包级注释。这是一个例子:

    Package[] packages = Package.getPackages();
    for (Package p : packages) {
        MyPackageAnnotation annotation = p.getAnnotation(MyPackageAnnotation.class);
        if (annotation != null) {
            Class<?>[]  implementations = annotation.implementationsOfPet();
            for (Class<?> impl : implementations) {
                System.out.println(impl.getSimpleName());
            }
        }
    }
    

    和注释定义:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.PACKAGE)
    public @interface MyPackageAnnotation {
        Class<?>[] implementationsOfPet() default {};
    }
    

    并且您必须package-info.java在该包内名为的文件中声明包级注释。以下是示例内容:

    @MyPackageAnnotation(implementationsOfPet = {Dog.class, Cat.class})
    package examples.reflections;
    

    请注意,只有那时ClassLoader已知的软件包才能通过调用加载Package.getPackages()

另外,还有其他基于URLClassLoader的方法将始终限于已加载的类,除非您执行基于目录的搜索。


三种方法中哪种更有效,最快?
carlspring '16

@carlspring我不能对它们的相对效率做出绝对的陈述,但是第一个选择(反射库)对我来说效果很好。
艾哈迈德·阿卜杜勒加尼

JDBC的DriverManager如何工作?它不是在做类似的事情(在类路径中搜索Driver接口的所有实现)吗?
Alex Semeniuk '19

1
@AlexSemeniuk我认为根据他们的文档“ DriverManager方法getConnection和getDrivers已得到增强,以支持Java Standard Edition Service Provider机制”,他们现在支持Service Loader / Provider机制(上述方法2)。请参阅docs.oracle.com/javase/8/docs/api/java/sql/DriverManager.html
Ahmad Abdelghany19年

1
值得注意的是,服务提供程序机制已通过Java 9集成到模块系统中,这使其比包级注释更方便,即,您无需创建自己的注释类型,即可在模块中声明实现-info,无论如何,在编写模块化软件时都会创建,并获得有关实现有效性的编译时反馈。
Holger

28

埃里克森说了什么,但是如果您仍然想这样做,那么请看《思考》。从他们的页面:

使用反射,您可以查询元数据以:

  • 获取某种类型的所有子类型
  • 使所有类型都带有一些注释
  • 获取所有带有注释的注释类型,包括匹配的注释参数
  • 得到所有带有一些注释的方法

12
更具体:new Reflections("my.package").getSubTypesOf(MyInterface.class)
zapp

27

通常,这样做很昂贵。要使用反射,必须加载该类。如果要加载类路径上可用的每个类,则将花费时间和内存,因此不建议这样做。

如果要避免这种情况,则需要实现自己的类文件解析器,该类解析器的运行效率更高,而不是反射。字节码工程库可能有助于此方法。

服务提供机制是枚举可插拔服务过程中的常规手段,并已成为更成熟的在Java 9.使用引进Jigsaw项目(模块)的ServiceLoaderJava 6中,或实现自己的早期版本。我在另一个答案中提供了一个示例


4
不幸的是,服务提供者机制要求您在一个通常不可行的单独文件中列出可能在关注列表中的类。
averasko 2015年

4
编写实现接口的源代码,编译和部署它是可行的,但是在实现的FQN中包含文本文件是不可行的?很少,甚至永远不会这样。很少“经常”。
erickson

JDBC的DriverManager如何工作?它不是在做类似的事情(在类路径中搜索Driver接口的所有实现)吗?
Alex Semeniuk

1
@AlexSemeniuk不。它使用我上面描述的服务加载程序机制;JDBC 4.0+驱动程序必须在META-INF/services/java.sql.Driver
erickson

14

Spring有一个非常简单的方法来实现这一点:

public interface ITask {
    void doStuff();
}

@Component
public class MyTask implements ITask {
   public void doStuff(){}
}

然后,您可以自动连接类型列表,ITaskSpring会使用所有实现填充它:

@Service
public class TaskService {

    @Autowired
    private List<ITask> tasks;
}

4
不完全是,Spring会将所有IType类型的bean填充到其中。
奥利维尔·盖拉丁(OlivierGérardin)

5

当前列出实现给定接口的所有类的最健壮的机制是ClassGraph,因为它可以处理尽可能广泛的类路径规范机制,包括新的JPMS模块系统。(我是作者。)

try (ScanResult scanResult = new ClassGraph().whitelistPackages("x.y.z")
        .enableClassInfo().scan()) {
    for (ClassInfo ci : scanResult.getClassesImplementing("x.y.z.SomeInterface")) {
        foundImplementingClass(ci);  // Do something with the ClassInfo object
    }
}

1
ClassGraph库的运行就像一个沙姆,谢谢@Luke Hutchison
Kyrylo Semenko



4

使用ClassGraph,它非常简单:

用Groovy代码查找以下实现my.package.MyInterface

@Grab('io.github.classgraph:classgraph:4.6.18')
import io.github.classgraph.*
new ClassGraph().enableClassInfo().scan().withCloseable { scanResult ->
    scanResult.getClassesImplementing('my.package.MyInterface').findAll{!it.abstract}*.name
}

您需要scan().withCloseable { ... }使用Groovy进行调用,或者使用Java中的try-with-resources:github.com/classgraph/classgraph/wiki/…。此外,最后一部分应该是.name,而不是.className,因为这.getName()是获取类名称的正确方法。从一个ClassInfo对象。
卢克·哈奇森

2

@ kaybee99的答案的新版本,但现在返回用户要求的内容:实现...

Spring有一个非常简单的方法来实现这一点:

public interface ITask {
    void doStuff();
    default ITask getImplementation() {
       return this;
    }

}

@Component
public class MyTask implements ITask {
   public void doStuff(){}
}

然后,您可以自动连接类型列表,ITaskSpring会使用所有实现填充它:

@Service
public class TaskService {

    @Autowired(required = false)
    private List<ITask> tasks;

    if ( tasks != null)
    for (ITask<?> taskImpl: tasks) {
        taskImpl.doStuff();
    }   
}

1

另外,如果您正在编写IDE插件(您尝试做的事情比较普遍),那么IDE通常会为您提供更有效的方法来访问用户代码当前状态的类层次结构。


1

我遇到了同样的问题。我的解决方案是使用反射检查ObjectFactory类中的所有方法,消除那些不是createXXX()方法的方法,这些方法返回我绑定的POJO之一的实例。如此发现的每个类都添加到Class []数组中,然后将其传递给JAXBContext实例化调用。这执行得很好,只需要加载ObjectFactory类,无论如何都将需要它。我只需要维护ObjectFactory类,该任务可以手动执行(在我的情况下,因为我是从POJO开始并使用了schemagen),或者可以根据需要由xjc生成。无论哪种方式,它都是高效,简单和有效的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.