在运行时,查找 Java 应用程序中扩展基类的所有类

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/205573/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 11:21:40  来源:igfitidea点击:

At runtime, find all classes in a Java application that extend a base class

javareflectioninheritance

提问by JohnnyLambada

I want to do something like this:

我想做这样的事情:

List<Animal> animals = new ArrayList<Animal>();

for( Class c: list_of_all_classes_available_to_my_app() )
   if (c is Animal)
      animals.add( new c() );

So, I want to look at all of the classes in my application's universe, and when I find one that descends from Animal, I want to create a new object of that type and add it to the list. This allows me to add functionality without having to update a list of things. I can avoid the following:

因此,我想查看应用程序 Universe 中的所有类,当我找到一个来自 Animal 的类时,我想创建一个该类型的新对象并将其添加到列表中。这使我无需更新事物列表即可添加功能。我可以避免以下情况:

List<Animal> animals = new ArrayList<Animal>();
animals.add( new Dog() );
animals.add( new Cat() );
animals.add( new Donkey() );
...

With the above approach, I can simply create a new class that extends Animal and it'll get picked up automatically.

使用上述方法,我可以简单地创建一个扩展 Animal 的新类,它会自动被选中。

UPDATE: 10/16/2008 9:00 a.m. Pacific Standard Time:

更新:2008 年 10 月 16 日上午 9:00 太平洋标准时间:

This question has generated a lot of great responses -- thank you. From the responses and my research, I've found that what I really want to do is just not possible under Java. There are approaches, such as ddimitrov's ServiceLoader mechanism that can work -- but they are very heavy for what I want, and I believe I simply move the problem from Java code to an external configuration file.Update 5/10/19(11 years later!) There are now several libraries that can help with this according to @IvanNik's answerorg.reflectionslooks good. Also ClassGraphfrom @Luke Hutchison's answerlooks interesting. There are several more possibilities in the answers as well.

这个问题引起了很多很好的回应——谢谢。 从回复和我的研究中,我发现我真正想做的事情在 Java 下是不可能的。有一些方法,例如 ddimitrov 的 ServiceLoader 机制可以工作——但它们对于我想要的东西来说非常繁重,我相信我只是将问题从 Java 代码转移到外部配置文件。2019 年 5月 10 日更新(11 年后!)根据@IvanNik 的回答org.reflections看起来不错,现在有几个库可以帮助解决这个问题。此外ClassGraph从@Luke和记黄埔的答案看起来很有趣。答案中还有更多的可能性。

Another way to state what I want: a static function in my Animal class finds and instantiates all classes that inherit from Animal -- without any further configuration/coding. If I have to configure, I might as well just instantiate them in the Animal class anyway. I understand that because a Java program is just a loose federation of .class files that that's just the way it is.

另一种说明我想要的方式:我的 Animal 类中的静态函数查找并实例化从 Animal 继承的所有类——无需任何进一步的配置/编码。如果我必须配置,我不妨在 Animal 类中实例化它们。我明白这是因为 Java 程序只是 .class 文件的松散联合,这就是它的方式。

Interestingly, it seems this is fairly trivialin C#.

有趣的是,这在 C# 中似乎相当简单

采纳答案by IvanNik

I use org.reflections:

我使用org.reflections

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

Another example:

另一个例子:

public static void main(String[] args) throws IllegalAccessException, InstantiationException {
    Reflections reflections = new Reflections("java.util");
    Set<Class<? extends List>> classes = reflections.getSubTypesOf(java.util.List.class);
    for (Class<? extends List> aClass : classes) {
        System.out.println(aClass.getName());
        if(aClass == ArrayList.class) {
            List list = aClass.newInstance();
            list.add("test");
            System.out.println(list.getClass().getName() + ": " + list.size());
        }
    }
}

回答by thoroughly

Java dynamically loads classes, so your universe of classes would be only those that have already been loaded (and not yet unloaded). Perhaps you can do something with a custom class loader that could check the supertypes of each loaded class. I don't think there's an API to query the set of loaded classes.

Java 动态加载类,因此您的类世界将只是那些已经加载(尚未卸载)的类。也许您可以使用自定义类加载器来检查每个加载类的超类型。我不认为有一个 API 来查询加载的类集。

回答by jonathan-stafford

Unfortunately this isn't entirely possible as the ClassLoader won't tell you what classes are available. You can, however, get fairly close doing something like this:

不幸的是,这并非完全可能,因为 ClassLoader 不会告诉您哪些类可用。但是,您可以非常接近地执行以下操作:

for (String classpathEntry : System.getProperty("java.class.path").split(System.getProperty("path.separator"))) {
    if (classpathEntry.endsWith(".jar")) {
        File jar = new File(classpathEntry);

        JarInputStream is = new JarInputStream(new FileInputStream(jar));

        JarEntry entry;
        while( (entry = is.getNextJarEntry()) != null) {
            if(entry.getName().endsWith(".class")) {
                // Class.forName(entry.getName()) and check
                //   for implementation of the interface
            }
        }
    }
}

Edit:johnstok is correct (in the comments) that this only works for standalone Java applications, and won't work under an application server.

编辑:johnstok 是正确的(在评论中),这仅适用于独立的 Java 应用程序,不能在应用程序服务器下工作。

回答by pdeva

This is a tough problem and you will need to find out this information using static analysis, its not available easily at runtime. Basically get the classpath of your app and scan through the available classes and read the bytecode information of a class which class it inherits from. Note that a class Dog may not directly inherit from Animal but might inherit from Pet which is turn inherits from Animal,so you will need to keep track of that hierarchy.

这是一个棘手的问题,您需要使用静态分析找出这些信息,它在运行时不容易获得。基本上获取您的应用程序的类路径并扫描可用类并读取它从哪个类继承的类的字节码信息。请注意,Dog 类可能不会直接从 Animal 继承,而是可能从 Pet 继承,而 Pet 又从 Animal 继承,因此您需要跟踪该层次结构。

回答by JohnnyLambada

Thanks all who answered this question.

感谢所有回答这个问题的人。

It seems this is indeed a tough nut to crack. I ended up giving up and creating a static array and getter in my baseclass.

看来这确实是一个难以破解的难题。我最终放弃并在我的基类中创建了一个静态数组和 getter。

public abstract class Animal{
    private static Animal[] animals= null;
    public static Animal[] getAnimals(){
        if (animals==null){
            animals = new Animal[]{
                new Dog(),
                new Cat(),
                new Lion()
            };
        }
        return animals;
    }
}

It seems that Java just isn't set up for self-discoverability the way C# is. I suppose the problem is that since a Java app is just a collection of .class files out in a directory / jar file somewhere, the runtime doesn't know about a class until it's referenced. At that time the loader loads it -- what I'm trying to do is discover it before I reference it which is not possible without going out to the file system and looking.

似乎 Java 并没有像 C# 那样设置为可自我发现。我想问题在于,由于 Java 应用程序只是某个目录/jar 文件中的 .class 文件的集合,因此运行时在引用类之前不知道该类。那时加载程序加载它——我想做的是在我引用它之前发现它,如果不去文件系统并查看,这是不可能的。

I always like code that can discover itself instead of me having to tell it about itself, but alas this works too.

我总是喜欢可以发现自己的代码,而不是我必须告诉它关于它自己,但可惜这也有效。

Thanks again!

再次感谢!

回答by JohnnyLambada

One way is to make the classes use a static initializers... I don't think these are inherited (it won't work if they are):

一种方法是让类使用静态初始值设定项......我不认为这些是继承的(如果它们是的话就行不通):

public class Dog extends Animal{

static
{
   Animal a = new Dog();
   //add a to the List
}

It requires you to add this code to all of the classes involved. But it avoids having a big ugly loop somewhere, testing every class searching for children of Animal.

它要求您将此代码添加到所有涉及的类中。但它避免了在某处出现一个丑陋的大循环,测试每个类以寻找 Animal 的孩子。

回答by Paul Sonier

Think about this from an aspect-oriented point of view; what you want to do, really, is know all the classes at runtime that HAVE extended the Animal class. (I think that's a slightly more accurate description of your problem than your title; otherwise, I don't think you have a runtime question.)

从面向方面的角度考虑这个问题;你真正想要做的是在运行时知道所有扩展了 Animal 类的类。(我认为这比标题更准确地描述了您的问题;否则,我认为您没有运行时问题。)

So what I think you want is to create a constructor of your base class (Animal) which adds to your static array (I prefer ArrayLists, myself, but to each their own) the type of the current Class which is being instantiated.

所以我认为你想要的是创建你的基类(动物)的构造函数,它添加到你的静态数组(我更喜欢 ArrayLists,我自己,但每个人自己)正在实例化的当前类的类型。

So, roughly;

所以,大致;

public abstract class Animal
    {
    private static ArrayList<Class> instantiatedDerivedTypes;
    public Animal() {
        Class derivedClass = this.getClass();
        if (!instantiatedDerivedClass.contains(derivedClass)) {
            instantiatedDerivedClass.Add(derivedClass);
        }
    }

Of course, you'll need a static constructor on Animal to initialize instantiatedDerivedClass... I think this'll do what you probably want. Note that this is execution-path dependent; if you have a Dog class that derives from Animal that never gets invoked, you won't have it in your Animal Class list.

当然,你需要在 Animal 上有一个静态构造函数来初始化instantiatedDerivedClass......我认为这会做你可能想要的。请注意,这是依赖于执行路径的;如果您的 Dog 类从永远不会被调用的 Animal 派生,则您的 Animal 类列表中将不会有它。

回答by ddimitrov

The Java way to do what you want is to use the ServiceLoadermechanism.

做您想做的事情的 Java 方法是使用ServiceLoader机制。

Also many people roll their own by having a file in a well known classpath location (i.e. /META-INF/services/myplugin.properties) and then using ClassLoader.getResources()to enumerate all files with this name from all jars. This allows each jar to export its own providers and you can instantiate them by reflection using Class.forName()

此外,许多人通过在众所周知的类路径位置(即 /META-INF/services/myplugin.properties)中放置一个文件然后使用ClassLoader.getResources()枚举所有 jar 中具有此名称的所有文件来推出自己的文件。这允许每个 jar 导出自己的提供者,您可以使用 Class.forName() 通过反射来实例化它们

回答by Adam Gent

I solved this problem pretty elegantly using Package Level Annotations and then making that annotation have as an argument a list of classes.

我使用包级注释非常优雅地解决了这个问题,然后使该注释具有类列表作为参数。

Find Java classes implementing an interface

查找实现接口的 Java 类

Implementations just have to create a package-info.java and put the magic annotation in with the list of classes they want to support.

实现只需要创建一个 package-info.java 并将魔法注释放入他们想要支持的类列表中。

回答by DJDaveMark

You could use ResolverUtil(raw source) from the Stripes Framework
if you need something simple and quick without refactoring any existing code.

如果您需要简单快速的东西而无需重构任何现有代码,您可以使用来自Stripes Framework 的ResolverUtil原始源代码)。

Here's a simple example not having loaded any of the classes:

这是一个没有加载任何类的简单示例:

package test;

import java.util.Set;
import net.sourceforge.stripes.util.ResolverUtil;

public class BaseClassTest {
    public static void main(String[] args) throws Exception {
        ResolverUtil<Animal> resolver = new ResolverUtil<Animal>();
        resolver.findImplementations(Animal.class, "test");
        Set<Class<? extends Animal>> classes = resolver.getClasses();

        for (Class<? extends Animal> clazz : classes) {
            System.out.println(clazz);
        }
    }
}

class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
class Donkey extends Animal {}

This also works in an application server as well since that's where it was designed to work ;)

这也适用于应用程序服务器,因为它是设计用来工作的;)

The code basically does the following:

该代码主要执行以下操作:

  • iterate over all the resources in the package(s) you specify
  • keep only the resources ending in .class
  • Load those classes using ClassLoader#loadClass(String fullyQualifiedName)
  • Checks if Animal.class.isAssignableFrom(loadedClass);
  • 迭代您指定的包中的所有资源
  • 只保留以 .class 结尾的资源
  • 使用加载这些类 ClassLoader#loadClass(String fullyQualifiedName)
  • 检查是否 Animal.class.isAssignableFrom(loadedClass);