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

我想这样做:

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

因此,我想查看应用程序中的所有类,当我找到一个从 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 的 回答 Org 反射看起来不错,现在有几个库可以帮助解决这个问题。同样来自@Luke Hutchison 的 回答课程表看起来也很有趣。答案中还有几种可能性。

另一种说明我想要什么的方法是: 在我的 Animal 类中使用一个静态函数来查找和实例化所有从 Animal 继承的类——不需要任何进一步的配置/编码。如果必须进行配置,我还不如直接在 Animal 类中实例化它们。我理解这一点,因为 Java 程序只是一个松散的。类文件,这就是它的方式。

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

169084 次浏览

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

不幸的是,这是不完全可能的,因为 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 应用程序,而不适用于应用服务器。

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

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

看来这确实是一个棘手的问题。我最终放弃了,在基类中创建了一个静态数组和 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 文件中,运行时在引用之前不会知道类的存在。这时加载程序会加载它——我要做的就是在引用它之前发现它,如果不进入文件系统查看,这是不可能的。

我总是喜欢能够发现自己的代码,而不是我必须告诉它关于它自己,但是唉,这也可以工作。

再次感谢!

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

public class Dog extends Animal{


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

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

从面向方面的角度考虑这个问题; 实际上,您想要做的是了解在运行时扩展 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 上使用一个静态构造函数来初始化 instantiatedderiedClass... ... 我认为这样就可以实现您可能想要的功能。请注意,这与执行路径有关; 如果从 Animal 派生的 Dog 类从未被调用,则 Animal Class 列表中不会包含它。

Java 实现目标的方法是使用 ServiceLoader机制。

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

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

查找实现接口的 Java 类

实现只需要创建一个包-info.java,并将这个神奇的注释与它们想要支持的类列表放在一起。

我使用 Org 反射:

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

您可以从 条纹框架使用 坚定不移(原材料)
如果您需要一些简单而快速的代码,而不需要重构任何现有的代码。

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

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

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

用这个

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

编辑:

列出给定类的所有子类的最健壮的机制当前是 课程表,因为它处理 尽可能广泛的类路径规范机制阵列,包括新的 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);
}

由于不推荐直接使用 newInstance(),因此可以使用 反光以这种方式进行。

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