简体   繁体   English

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

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

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.因此,我想查看应用程序 Universe 中的所有类,当我找到一个来自 Animal 的类时,我想创建一个该类型的新对象并将其添加到列表中。 This allows me to add functionality without having to update a list of things.这使我无需更新事物列表即可添加功能。 I can avoid the following:我可以避免以下情况:

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 am 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.从回复和我的研究中,我发现我真正想做的事情在 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.有一些方法,例如 ddimitrov 的 ServiceLoader 机制可以工作——但它们对于我想要的东西来说非常繁重,我相信我只是将问题从 Java 代码转移到外部配置文件。 Update 5/10/19 (11 years later!) There are now several libraries that can help with this according to @IvanNik's answer org.reflections looks good. 2019 年 5月 10 日更新(11 年后!)根据@IvanNik 的回答org.reflections看起来不错,现在有几个库可以帮助解决这个问题 Also ClassGraph from @Luke Hutchison's answer looks interesting.此外ClassGraph从@Luke和记黄埔的答案看起来很有趣。 There are several more possibilities in the answers as well.答案中还有更多的可能性。

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.另一种说明我想要的方式:我的 Animal 类中的静态函数查找并实例化从 Animal 继承的所有类——无需任何进一步的配置/编码。 If I have to configure, I might as well just instantiate them in the Animal class anyway.如果我必须配置,我不妨在 Animal 类中实例化它们。 I understand that because a Java program is just a loose federation of .class files that that's just the way it is.我明白这是因为 Java 程序只是 .class 文件的松散联合,这就是它的方式。

Interestingly, it seems this is fairly trivial in C#.有趣的是,这在 C# 中似乎相当简单

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

The Java way to do what you want is to use the ServiceLoader mechanism.做您想做的事情的 Java 方法是使用ServiceLoader机制。

Also many people roll their own by having a file in a well known classpath location (ie /META-INF/services/myplugin.properties) and then using ClassLoader.getResources() to enumerate all files with this name from all jars.许多人也通过在众所周知的类路径位置(即 /META-INF/services/myplugin.properties)中放置一个文件然后使用ClassLoader.getResources()枚举所有 jar 中具有此名称的所有文件来推出自己的文件。 This allows each jar to export its own providers and you can instantiate them by reflection using Class.forName()这允许每个 jar 导出自己的提供者,您可以使用 Class.forName() 通过反射来实例化它们

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.你真正想要做的是在运行时知道所有扩展了 Animal 类的类。 (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.) (我认为这比标题更准确地描述了您的问题;否则,我认为您没有运行时问题。)

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.当然,你需要在 Animal 上有一个静态构造函数来初始化instantiatedDerivedClass......我认为这会做你可能想要的。 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.如果您的 Dog 类从永远不会被调用的 Animal 派生,则您的 Animal 类列表中将不会有它。

Unfortunately this isn't entirely possible as the ClassLoader won't tell you what classes are available.不幸的是,这并非完全可能,因为 ClassLoader 不会告诉您哪些类可用。 You can, however, get fairly close doing something like this:但是,您可以非常接近地执行以下操作:

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 应用程序,不能在应用程序服务器下工作。

The most robust mechanism for listing all subclasses of a given class is currently ClassGraph , because it handles the widest possible array of classpath specification mechanisms , including the new JPMS module system.列出给定类的所有子类的最健壮的机制目前是ClassGraph ,因为它处理最广泛的类路径规范机制,包括新的 JPMS 模块系统。 (I am the author.) (我是作者。)

List<Class<Animal>> animals;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("com.zoo.animals")
        .enableClassInfo().scan()) {
    animals = scanResult
        .getSubclasses(Animal.class.getName())
        .loadClasses(Animal.class);
}

You could use ResolverUtil ( raw source ) from the Stripes Framework您可以使用Stripes Framework 中的ResolverUtil原始源
if you need something simple and quick without refactoring any existing code.如果您需要简单快速的东西而无需重构任何现有代码。

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只保留以 .class 结尾的资源
  • Load those classes using ClassLoader#loadClass(String fullyQualifiedName)使用ClassLoader#loadClass(String fullyQualifiedName)加载这些类
  • Checks if Animal.class.isAssignableFrom(loadedClass);检查是否Animal.class.isAssignableFrom(loadedClass);

Java dynamically loads classes, so your universe of classes would be only those that have already been loaded (and not yet unloaded). Java 动态加载类,因此您的类世界将只是那些已经加载(尚未卸载)的类。 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.我不认为有一个 API 来查询加载的类集。

use this用这个

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 (ResolverUtil) 的库:条纹

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

I always like code that can discover itself instead of me having to tell it about itself, but alas this works too.我总是喜欢可以发现自己的代码,而不是我必须告诉它关于它自己,但可惜这也有效。

Thanks again!再次感谢!

Using OpenPojo you can do the following:使用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));
}

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 继承,因此您需要跟踪该层次结构。

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 的孩子。

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 并将魔法注释放入他们想要支持的类列表中。

Since directly using newInstance() is deprecated, you can do it this way using Reflections .由于不推荐直接使用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.
});

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Java-获取扩展抽象类的所有类 - Java - Getting all the classes that extend an Abstract Class 查找运行时Java可用的所有类 - Find all classes availible at runtime java Java:当其他类已经扩展基类时,如何扩展基类? - Java: How to extend a base class when other classes already extend the base? Java设计:向扩展相同基类的类添加通用功能,而无需修改基类 - Java design: Adding common functionality to classes that extend the same base class, without modifying base class 如何识别实现不扩展某些基类的特定接口的所有类? - How to identify all classes implementing a specific interface that do NOT extend some base class? 获取模块 java.base 中所有类的 Class 对象 - Get the Class objects for all classes in the module java.base Eclipse - 查找扩展接口的所有类 - Eclipse - find all classes that extend interface 在Java中,匿名类可以扩展另一个类吗? - In Java, can anonymous classes extend another class? 一个类如何在Java中扩展两个类? - How can a class extend two classes in Java? 如何找到扩展类X的类并检查在Java中它们内的任何地方是否使用了try / catch块 - How to find classes which extend class X and check if a try/catch block is used anywhere inside them in Java
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM