IT박스

런타임시 기본 클래스를 확장하는 Java 애플리케이션에서 모든 클래스를 찾으십시오.

itboxs 2020. 6. 21. 20:29
반응형

런타임시 기본 클래스를 확장하는 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시 태평양 표준시 :

이 질문은 많은 훌륭한 반응을 가져 왔습니다. 감사합니다. 응답과 연구에서 필자가 실제로하고 싶은 것은 Java에서는 불가능하다는 것을 알았습니다. ddimitrov의 ServiceLoader 메커니즘과 같은 접근 방식이 작동하지만 원하는만큼 무겁기 때문에 문제를 Java 코드에서 외부 구성 파일로 옮긴다 고 생각합니다. 업데이트 5/10/19 (11 년 후!) @IvanNik의 답변 org.reflections 에 따라 이것을 도울 수있는 몇 가지 라이브러리가 있습니다 . @Luke Hutchison의 답변 에서 나온 ClassGraph 흥미로워 보입니다. 답변에는 몇 가지 가능성이 더 있습니다.

내가 원하는 것을 나타내는 또 다른 방법 : Animal 클래스의 정적 함수는 추가 구성 / 코딩없이 Animal에서 상속되는 모든 클래스를 찾아 인스턴스화합니다. 구성 해야하는 경우 어쨌든 Animal 클래스에서 인스턴스화 할 수도 있습니다. Java 프로그램은 .class 파일의 느슨한 페더레이션이기 때문에 이해합니다.

흥미롭게도 C #에서는 이것이 사소한 것 같습니다 .


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

원하는 것을 수행하는 Java 방법은 ServiceLoader 메커니즘 을 사용하는 것 입니다.

또한 많은 사람들이 잘 알려진 클래스 경로 위치 (예 : /META-INF/services/myplugin.properties)에 파일을 둔 다음 ClassLoader.getResources ()사용하여 모든 jar에서이 이름을 가진 모든 파일을 열거 함으로써 자신의 롤 을 생성합니다. 이를 통해 각 jar는 자체 공급자를 내보낼 수 있으며 Class.forName ()을 사용하여 반영하여 인스턴스화 할 수 있습니다.


측면 지향적 인 관점에서 이것을 생각하십시오. 실제로하고 싶은 것은 런타임에 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);
        }
    }

물론, instantiatedDerivedClass를 초기화하려면 Animal에 정적 생성자가 필요합니다. 아마도 이것이 원하는 것을 할 것이라고 생각합니다. 이것은 실행 경로에 따라 다릅니다. Animal에서 파생 된 Dog 클래스가 호출되지 않은 경우 Animal 클래스 목록에 없습니다.


불행히도 이것은 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
            }
        }
    }
}

Edit: johnstok is correct (in the comments) that this only works for standalone Java applications, and won't work under an application server.


You could use ResolverUtil (raw source) from the Stripes Framework
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
  • Load those classes using ClassLoader#loadClass(String fullyQualifiedName)
  • Checks if Animal.class.isAssignableFrom(loadedClass);

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

Java dynamically loads classes, so your universe of classes would be only those that have already been loaded (and not yet unloaded). 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.


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

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.

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. 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. 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:

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.


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.


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

Implementations just have to create a package-info.java and put the magic annotation in with the list of classes they want to support.

참고URL : https://stackoverflow.com/questions/205573/at-runtime-find-all-classes-in-a-java-application-that-extend-a-base-class

반응형