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


147

我想做这样的事情:

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

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

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

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

更新:10/16/2008 9:00 AM太平洋标准时间:

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

声明我想要的另一种方式:Animal类中的静态函数查找并实例化了从Animal继承的所有类-无需任何进一步的配置/编码。如果必须进行配置,无论如何,我还是可以在Animal类中实例化它们。我了解这是因为Java程序只是.class文件的松散联合,而事实就是如此。

有趣的是,这似乎在C#中是微不足道的


1
经过一段时间的搜索之后,似乎很难在Java中破解。这是一个包含一些信息的线程: forums.sun.com/thread.jspa?threadID=341935&start=15 其中的实现超出了我的需要,我想我现在还是坚持使用第二种实现。
JohnnyLambada

把它作为答案,让读者投票
VonC

3
在C#中执行此操作的链接未显示任何特殊内容。AC#Assembly等效于Java JAR文件。它是已编译的类文件(和资源)的集合。该链接显示了如何从单个程序集中获取类。您可以使用Java几乎轻松地做到这一点。您遇到的问题是,您需要浏览所有JAR文件和真正的松散文件(目录)。您将需要对.NET进行相同的操作(搜索某种或多种PATH)。
凯文·布洛克

Answers:


156

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

6
感谢您提供有关org.reflections的链接。我错误地认为这就像在.Net世界中一样容易,而且此答案为我节省了很多时间。
akmad 2012年

1
确实,感谢您对有用的软件包“ org.reflections”的有用链接!有了这些,我终于找到了一个切实可行的解决方案。
Hartmut P.

3
有没有一种方法可以获取所有反射,即不注意特定的程序包而是加载所有可用的类?
Joey Baruch

请记住,找到类并不能解决实例化它们的问题(animals.add( new c() );代码的第5行),因为尽管Class.newInstance()存在,但您无法保证特定的类具有无参数的构造函数(C#允许您在泛型中要求它)
usr-local-ΕΨΗΕΛΩΝ18

另请参见ClassGraph:github.com/classgraph/classgraph(免责声明,我是作者)。我将在一个单独的答案中给出一个代码示例。
卢克·哈奇森

34

执行所需操作的Java方法是使用ServiceLoader机制。

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


1
为了根据类的注释轻松生成META-INF服务文件,您可以检查:metainf-services.kohsuke.orgcode.google.com/p/spi
elek 2012年

le 只是增加了一定程度的复杂性,但并没有消除创建一个类然后在很远的地方注册它的需要。
凯文·克莱恩

请查看以下带有26个投票的答案,更好的是
SobiborTreblinka 2014年

4
的确,如果您需要一种可行的解决方案,则反射/扫描注释是一个不错的选择,但是它们都具有外部依赖性,是不确定的,并且不受Java Community Process的支持。另外,我想强调一点,您永远无法可靠地获得实现接口的所有类,因为随时随地凭空制造一个新的类是微不足道的。对于Java的所有服务缺陷,它都很容易理解和使用。由于种种原因,我会远离它,但后来类路径扫描变得更糟:stackoverflow.com/a/7237152/18187
ddimitrov 2014年

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上使用一个静态构造函数来初始化InstantiatedDerivedClass ...我认为这会做您可能想要的事情。注意,这与执行路径有关;如果您有一个从动物派生而来的Dog类,并且从未被调用,那么您就不会在Animal Class列表中找到它。


6

不幸的是,这并非完全可能,因为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,

如果您URL[]从ClassLoader层次结构中查询路径(通常但并非总是如此,则可能无法做到),即使在容器下也可能会做得更好。尽管通常您必须具有许可权,因为通常您会SecurityManager在JVM中进行加载。
凯文·布洛克

对我来说就像一个魅力!我担心遍历类路径中的所有类会太繁琐且太慢,但实际上我将检查的文件限制为包含“ -ejb-”或其他可识别文件名的文件,它仍然比启动快100倍嵌入式glassfish或EJBContainer。在我的特定情况下,然后我分析了寻找“无状态”,“有状态”和“ Singleton”注释的类,如果看到它们,则将JNDI Mapped名称添加到我的MockInitialContextFactory中。再次感谢!
cs94njw 2013年

4

如果您需要简单快捷的方法而无需重构任何现有代码,则可以使用Stripes Framework中的ResolverUtil原始源)。

这是一个简单的示例,未加载任何类:

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

4

列出给定类的所有子类的最健壮的机制是ClassGraph,因为它可以处理最广泛的类路径规范机制,包括新的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 >>
hiaclibe19年

2

Java动态地加载类,因此您的类的范围将仅是那些已经加载(但尚未卸载)的类。也许您可以使用自定义类加载器执行某些操作,该加载器可以检查每个已加载类的超类型。我认为没有API可以查询一组已加载的类。


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

编辑:

  • 库(ResolverUtil):条纹

2
您需要多谈ResolverUtil。它是什么?它来自哪个库?
JohnnyLambada

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文件的集合,因此运行库直到被引用才知道类。那时,加载程序加载了它-我想做的是在我引用它之前先发现它,这是不访问文件系统并查找的情况。

我一直喜欢可以发现自己的代码,而不必告诉我有关自身的信息,但是but,这也行得通。

再次感谢!


1
Java被设置为自我发现。您只需要做更多的工作。请参阅我的答案以获取详细信息。
戴夫·贾维斯

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

0

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


0

一种方法是使类使用静态初始化器...我不认为它们是继承的(如果它们是不起作用的):

public class Dog extends Animal{

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

它要求您将此代码添加到所有涉及的类中。但这避免了在某个地方出现一个大的丑陋循环,测试每个班级搜索Animal的孩子。


3
就我而言,这不起作用。由于没有在任何地方引用该类,因此永远不会加载它,因此永远不会调用静态初始化器。看来,在加载类时会先调用静态初始化程序,但在我看来,该类从未加载。
JohnnyLambada

0

我使用Package Level Annotations很好地解决了这个问题,然后使该注释具有一个类列表作为参数。

查找实现接口的Java类

实现只需创建一个package-info.java并将魔术批注放入要支持的类的列表中即可。

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.