【问题标题】:At runtime, find all classes in a Java application that extend a base class在运行时,查找 Java 应用程序中扩展基类的所有类
【发布时间】:2010-09-17 08:40:30
【问题描述】:

我想做这样的事情:

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:00 太平洋标准时间:

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

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

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

【问题讨论】:

  • 找了一阵子,看来这是Java中难以破解的难题。这是一个包含一些信息的线程:forums.sun.com/thread.jspa?threadID=341935&start=15 其中的实现比我需要的要多,我想我现在只坚持第二个实现。
  • 在 C# 中执行此操作的链接没有显示任何特别之处。 C# 程序集等同于 Java JAR 文件。它是编译的类文件(和资源)的集合。该链接显示了如何从单个程序集中获取类。使用 Java 几乎可以轻松地做到这一点。您的问题是您需要查看所有 JAR 文件和真正的松散文件(目录);您需要对 .NET 执行相同的操作(搜索某种或各种 PATH)。

标签: java reflection inheritance


【解决方案1】:

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

【讨论】:

  • 感谢有关 org.reflections 的链接。我错误地认为这就像在 .Net 世界中一样简单,这个答案为我节省了很多时间。
  • 非常感谢您提供这个指向伟大包“org.reflections”的有用链接!有了这个,我终于为我的问题找到了一个可行且简洁的解决方案。
  • 有没有办法获取所有反射,即不注意特定包,但加载所有可用类?
  • 请记住,查找类并不能解决实例化它们的问题(代码的第 5 行 animals.add( new c() );),因为虽然 Class.newInstance() 存在,但您无法保证特定类具有无参数构造函数(C# 允许您在泛型中要求它)
  • 参见 ClassGraph:github.com/classgraph/classgraph(免责声明,我是作者)。我将在单独的答案中给出一个代码示例。
【解决方案2】:

Java 做你想做的事是使用ServiceLoader 机制。

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

【讨论】:

  • 为了根据类的注解轻松生成META-INF服务文件,可以查看:metainf-services.kohsuke.orgcode.google.com/p/spi
  • 嘘。只是增加了一定程度的复杂性,但并没有消除创建一个类然后在很远的地方注册它的需要。
  • 确实,如果您需要一种可行的解决方案,Reflections / Scannotations 是一个不错的选择,但两者都强加了外部依赖,都是不确定的,并且不受 Java Community Process 的支持。此外,我想强调的是,您永远无法可靠地获得所有实现接口的类,因为随时凭空制造一个新的类是微不足道的。尽管有缺点,Java 的服务加载器很容易理解和使用。由于不同的原因,我会远离它,但类路径扫描更糟糕:stackoverflow.com/a/7237152/18187
【解决方案3】:

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

所以我认为你想要的是创建一个基类(Animal)的构造函数,它将正在实例化的当前类的类型添加到你的静态数组(我更喜欢 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... 我认为这会满足您的需求。请注意,这取决于执行路径;如果你有一个从 Animal 派生的 Dog 类,它永远不会被调用,那么你的 Animal Class 列表中就不会出现它。

【讨论】:

    【解决方案4】:

    不幸的是,这并不完全可能,因为 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 是正确的(在 cmets 中),这仅适用于独立 Java 应用程序,不适用于应用程序服务器。

    【讨论】:

    • 这在通过其他方式加载类时效果不佳,例如,在 Web 或 J2EE 容器中。
    • 如果您从 ClassLoader 层次结构中查询路径(通常是 URL[],但并非总是如此,则可能不可能),即使在容器下,您也可能会做得更好。通常,尽管您必须获得许可,因为通常您会在 JVM 中加载 SecurityManager
    • 对我来说就像一个魅力!我担心这会太重且太慢,遍历类路径中的所有类,但实际上我将检查的文件限制为包含“-ejb-”或其他可识别文件名的文件,它仍然比启动快 100 倍嵌入式 glassfish 或 EJBContainer。在我的特殊情况下,然后我分析了寻找“无状态”、“有状态”和“单例”注释的类,如果我看到它们,我将 JNDI 映射名称添加到我的 MockInitialContextFactory 中。再次感谢!
    【解决方案5】:

    目前列出给定类的所有子类的最强大的机制是ClassGraph,因为它处理widest possible array of classpath specification mechanisms,包括新的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> 类型
    【解决方案6】:

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

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

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

    【讨论】:

      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

        使用这个

        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 多说一些。它是什么?它来自哪个库?
        【解决方案9】:

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

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

        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 文件的集合,因此运行时在引用它之前不知道一个类。那时加载器会加载它——我想做的是在引用它之前发现它,这在不去文件系统和查看的情况下是不可能的。

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

        再次感谢!

        【讨论】:

        • Java 设置为自我发现。你只需要做更多的工作。详情见我的回答。
        【解决方案10】:

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

        【讨论】:

          【解决方案11】:

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

          【讨论】:

            【解决方案12】:

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

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

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

            【讨论】:

            • 这在我的情况下不起作用。由于该类在任何地方都没有被引用,因此它永远不会被加载,因此永远不会调用静态初始化程序。加载类时,似乎在其他所有操作之前调用了静态初始化程序——但在我的情况下,该类从未加载过。
            【解决方案13】:

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

            Find Java classes implementing an interface

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

            【讨论】:

              【解决方案14】:

              由于不推荐直接使用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.
              });
              

              【讨论】:

                猜你喜欢
                • 2013-06-15
                • 2010-12-07
                • 2013-10-14
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多