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

时间:2008-10-15 17:07:27

标签: java reflection inheritance

我想做这样的事情:

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的新类,它将自动被拾取。

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

这个问题引起了很多很好的回应 - 谢谢。 从回复和我的研究中,我发现我真正想做的事情在Java下是不可能的。有一些方法,比如ddimitrov的ServiceLoader机制可以工作 - 但它们对我想要的东西非常重,我相信我只是将问题从Java代码转移到外部配置文件。 更新5 / 10/19 (11年后!)根据@ IvanNik的answer org.reflections,现在有几个图书馆可以提供帮助。来自@Luke Hutchison ClassGraphanswer也很有趣。答案中还有几种可能性。

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

有趣的是,C#中似乎是fairly trivial

14 个答案:

答案 0 :(得分:144)

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

答案 1 :(得分:33)

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

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

通过反射实例化它们

答案 2 :(得分:8)

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

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

所以,粗略;

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类,它永远不会被调用,你就不会在动物类列表中拥有它。

答案 3 :(得分: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应用程序,并且不适用于应用程序服务器。

答案 4 :(得分:4)

您可以使用ResolverUtil中的raw sourceStripes Framework) 如果您需要简单快速的东西而不重构任何现有代码。

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

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

答案 5 :(得分:3)

尝试ClassGraph。 (免责声明,我是作者)。对于给定的示例,ClassGraph代码将为:

List<Animal> animals =
    new ClassGraph()
        .whitelistPackages("com.zoo.animals")
        .enableAllInfo()
        .scan()
        .getSubclasses(Animal.class.getName())
        .loadClasses(Animal.class);

ClassGraph还可以做更多的工作-check out the API

答案 6 :(得分:2)

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

答案 7 :(得分: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)的库:Stripes

答案 8 :(得分: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文件集合,运行时在引用之前不知道类。那时加载器加载它 - 我正在尝试做的是在我引用它之前发现它,这是不可能没有去文件系统并查看。

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

再次感谢!

答案 9 :(得分: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));
}

答案 10 :(得分:0)

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

答案 11 :(得分:0)

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

public class Dog extends Animal{

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

它要求您将此代码添加到所涉及的所有类中。但是它避免了某个地方有一个很大的丑陋循环,测试每一个寻找动物孩子的班级。

答案 12 :(得分:0)

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

Find Java classes implementing an interface

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

答案 13 :(得分:0)

由于直接使用 newInstance() 已被弃用,因此您可以使用 Reflections 这样做。

Reflections r = new Reflections("com.example.location.of.sub.classes")
Set<Class<? extends Animal>> classes = r.getSubTypesOf(Animal.class);

classes.forEach(c -> {
    Animal a = c.getDeclaredConstructor().newInstance();
    //a is your instance of Animal.
});