在运行时,查找Java应用程序中所有继承基类的类。

170

我想做类似这样的事情:

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() );
所以,我想查看应用程序宇宙中的所有类,并且当我找到一个继承自动物类的类时,我想创建一个该类型的新对象并将其添加到列表中。这样可以在不更新事物列表的情况下添加功能。我可以避免以下操作:
List<Animal> animals = new ArrayList<Animal>();
animals.add( new Dog() );
animals.add( new Cat() );
animals.add( new Donkey() );
...

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

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

这个问题引起了很多出色的回复--谢谢。从回复和我的研究中,我发现在Java下我真正想做的事情是不可能的。有一些方法,比如ddimitrov的ServiceLoader机制可以工作——但对于我想要的东西来说太重了,我相信我只是将问题从Java代码移动到一个外部配置文件中。更新 5/10/19(11年后!)根据@IvanNik的回答,现在有几个库可以帮助处理这个问题,org.reflections看起来不错。同时根据@Luke Hutchison的回答ClassGraph 也很有趣。还有几种可能性在答案中。

另一种陈述我想要的方式:我的Animal类中的静态函数找到并实例化所有继承自Animal的类——不需要进一步的配置/编码。如果我必须进行配置,那么我可能会在Animal类中直接实例化它们。我知道由于Java程序只是一组.class文件的松散联合,这就是事实。

有趣的是,这在C#中似乎很容易实现。


2
在搜索了一段时间后,似乎这是Java中一个难以解决的难题。这里有一个帖子提供了一些信息:http://forums.sun.com/thread.jspa?threadID=341935&start=15其中的实现比我需要的要多,我想我现在只需坚持使用第二个实现即可。 - JohnnyLambada
3
在C#中完成此操作的链接并没有显示任何特殊内容。 C#程序集相当于Java JAR文件,都是一组已编译的类文件(和资源)。该链接展示了如何从单个程序集中获取类。你可以用Java轻松地做到这一点。但问题在于,您需要查看所有JAR文件和真实的松散文件(目录);在.NET中也需要进行相同的操作(搜索某种路径或多种路径)。 - Kevin Brock
14个回答

177

我使用org.reflections

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

另一个例子:

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());
        }
    }
}

7
谢谢您提供有关org.reflections的链接。我错误地认为这会像在.NET世界中一样容易,而这个答案为我节省了很多时间。 - akmad
1
非常感谢您提供这个有用的链接,介绍了伟大的“org.reflections”包!有了它,我终于找到了一个实用而整洁的解决方案来解决我的问题。 - Hartmut Pfarr
3
有没有一种方法可以获取所有的反射,即不指定特定的包,而是加载所有可用的类? - Joey Baruch
@Kenji,你可以在对象或特定类的实例上调用方法。请参见下面的示例: - IvanNik
这就像 .Net 一样容易。 - Taha Rehman Siddiqui
显示剩余4条评论

36
使用Java实现你所需的功能的方式是使用ServiceLoader机制。 此外,许多人通过在已知类路径位置(即/META-INF/services/myplugin.properties)中拥有一个文件,然后使用ClassLoader.getResources()枚举所有具有此名称的文件(来自所有jars),以便每个jar可以导出其自己的提供程序,并且您可以使用Class.forName()反射实例化它们。

1
要根据类上的注释轻松生成META-INF服务文件,您可以查看:http://metainf-services.kohsuke.org/或https://code.google.com/p/spi/。 - elek
Bleah. 这只是增加了一些复杂性,但并不能消除在遥远的地方创建一个类然后注册它的需要。 - kevin cline
4
如果您需要某种工作解决方案,Reflections / Scannotations是一个不错的选择,但两者都需要外部依赖,是非确定性的,并且不受Java社区进程支持。此外,我想强调的是,您永远无法可靠地获取实现接口的所有类,因为随时可以轻松地从空气中制造一个新类。尽管有其缺点,Java的服务加载程序易于理解和使用。出于不同的原因,我会避免使用它,但是类路径扫描甚至更糟:https://dev59.com/jmw05IYBdhLWcg3wxkmr#7237152 - ddimitrov

11

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

所以我认为您想要创建基类(Animal)的构造函数,将当前正在实例化的Class类型添加到静态数组中(我自己更喜欢ArrayLists,但每个人都有自己的偏好)。

因此,大致上:

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

当然,您需要在Animal类上拥有一个静态构造函数来初始化实例化的DerivedClass...我认为这将做到您可能想要的。请注意,这取决于执行路径; 如果您有一个从Animal派生但从未被调用的Dog类,您将不会在Animal类列表中找到它。


9
目前寻找给定类的所有子类最强大的机制是ClassGraph,因为它处理了最广泛的classpath规范机制,包括新的JPMS模块系统。(我是作者。)
List<Class<Animal>> animals;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("com.zoo.animals")
        .enableClassInfo().scan()) {
    animals = scanResult
        .getSubclasses(Animal.class.getName())
        .loadClasses(Animal.class);
}

结果的类型是 List<Class<Animal>>。 - hiaclibe
有没有一种递归列出子类的方法?例如,我知道类A的类型,我想要类C。类C继承了类B,而类B又继承了类A。此外,ClassGraph一直是我的类路径扫描器首选,它非常快速和可靠。+1 - Cardinal System
@CardinalSystem 是的,请尝试使用 var subclasses = scanResult.getClassInfo("pkg.ClassName").getSubclasses()。这将获取整个子类图。 - Luke Hutchison

8

很不幸的是,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
            }
        }
    }
}

编辑: johnstok在评论中指出,这仅适用于独立的Java应用程序,在应用服务器下无法工作。


当类是由其他方式加载时,这种方法效果不佳,例如在Web或J2EE容器中。 - johnstok
如果您从ClassLoader层次结构中查询路径(通常为URL [],但不总是可能),即使在容器下,您可能会做得更好。然而,通常情况下,您必须获得权限,因为JVM中通常会加载SecurityManager - Kevin Brock
对我来说非常好用!我担心这会太重且慢,需要遍历类路径中的所有类,但实际上我将要检查的文件限制为包含“-ejb-”或其他可识别文件名的文件,它仍然比启动嵌入式glassfish或EJBContainer快100倍。在我的特定情况下,我分析了类,寻找“Stateless”,“Stateful”和“Singleton”注释,如果看到它们,我就将JNDI映射名称添加到我的MockInitialContextFactory中。再次感谢! - cs94njw

4
你可以使用ResolverUtil (源代码)来自Stripes框架,如果你需要快速简单的东西而不需要重构现有代码。
这里有一个简单的例子,没有加载任何类:
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 {}

这也适用于应用服务器,因为它就是为此而设计的;)
该代码基本上执行以下操作:
- 迭代指定包中的所有资源 - 仅保留以.class结尾的资源 - 使用ClassLoader#loadClass(String fullyQualifiedName)加载这些类 - 检查Animal.class.isAssignableFrom(loadedClass)是否成立

2

使用这个

public static Set<Class> getExtendedClasses(Class superClass)
{
    try
    {
        ResolverUtil resolver = new ResolverUtil();
        resolver.findImplementations(superClass, superClass.getPackage().getName());
        return resolver.getClasses();  
    }
    catch(Exception e)
    {Log.d("Log:", " Err: getExtendedClasses() ");}

    return null;
}

getExtendedClasses(Animals.class);

Edit:

  • library for (ResolverUtil) : Stripes

2
你需要多说一些关于ResolverUtil的内容。它是什么?它来自哪个库? - JohnnyLambada

2

Java可以动态加载类,因此您的类宇宙将仅包括已经加载(但尚未卸载)的类。也许您可以使用自定义类加载器来检查每个加载类的超类型。我不认为有API可以查询已加载类的集合。


1

使用OpenPojo,您可以执行以下操作:

String package = "com.mycompany";
List<Animal> animals = new ArrayList<Animal>();

for(PojoClass pojoClass : PojoClassFactory.enumerateClassesByExtendingType(package, Animal.class, null) {
  animals.add((Animal) InstanceFactory.getInstance(pojoClass));
}

1

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

看起来这确实是一个难以解决的难题。我最终放弃了,在我的基类中创建了一个静态数组和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;
    }
}

看起来Java并没有像C#那样为自我发现设置好。问题在于,由于Java应用程序只是某个目录/ jar文件中的一组.class文件集合,因此运行时不知道类,直到它被引用。此时加载器会加载它——我试图做的是在引用之前发现它,这是不可能的,除非到文件系统中查找。

我总是喜欢能够自我发现的代码而不是让我告诉它一切,但不幸的是这也可以。

再次感谢!


1
Java已经设置好自我探索。您只需要再多做一些工作。有关详细信息,请查看我的答案。 - Dave Jarvis

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