IT이야기

런타임에 기본 클래스를 확장하는 Java 응용 프로그램에서 모든 클래스 찾기

cyworld 2022. 6. 6. 10:37
반응형

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

이 질문은 많은 호응을 얻고 있습니다.감사합니다.응답과 조사 결과, 자바에서는 정말 하고 싶은 일이 불가능하다는 것을 알 수 있었습니다. ddimitrov의 Service Loader 메커니즘 등 동작할 수 있는 접근법이 있습니다.그러나 이 접근법은 제가 원하는 것에 대해 매우 무겁기 때문에 문제를 Java 코드에서 외부 설정 파일로 옮기기만 하면 된다고 생각합니다.업데이트 5/10/19 (11년 후)@IvanNik의 답변 조직에 의하면, 이것을 도와줄 수 있는 라이브러리가 몇개인가 있습니다.리플렉션은 좋아 보입니다.@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에서 이 이름의 모든 파일을 열거함으로써 자신의 파일을 롤합니다.이것에 의해, 각 자르는 독자적인 프로바이더를 export 할 수 있게 되어, 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);
        }
    }

물론 인스턴스화된 Derival Class를 초기화하려면 Animal의 정적 컨스트럭터가 필요합니다.이게 네가 원하는 걸 할 수 있을 것 같아.이 클래스는 실행 경로에 따라 달라지며, 호출되지 않는 Animal에서 파생된 Dog 클래스가 있는 경우 Animal Class 목록에 없습니다.

안타깝게도 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 어플리케이션에서만 동작하며 어플리케이션서버에서는 동작하지 않습니다.

특정 클래스의 모든 서브클래스를 나열하는 가장 강력한 메커니즘은 현재 ClassGraph입니다.는 새로운 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);
}

Stripes Framework에서 ResolverUtil(원시 소스)을 사용할 수 있습니다.
기존 코드를 수정하지 않고 단순하고 빠른 것이 필요한 경우

다음은 클래스를 로드하지 않은 간단한 예입니다.

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)
  • 크합니 checks 합니다.Animal.class.isAssignableFrom(loadedClass);

Java는 클래스를 동적으로 로드하므로 클래스 세계는 이미 로드된(아직 언로드되지 않은) 클래스만 됩니다.로드된 각 클래스의 수퍼 유형을 확인할 수 있는 사용자 지정 클래스 로더를 사용하여 작업을 수행할 수 있습니다.로드된 클래스 집합을 쿼리할 API가 없는 것 같습니다.

이것을 사용하다

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

편집:

이 질문에 대답해주신 모든 분들께 감사드립니다.

이것은 정말 해결하기 어려운 문제인 것 같다.결국 기본 클래스에서 정적 어레이와 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 파일의 모음일 뿐이기 때문에 런타임은 참조될 때까지 클래스에 대해 알지 못한다는 것입니다.로더가 로딩할 때 -- 제가 하려고 하는 것은 파일을 참조하기 전에 그것을 발견하는 것입니다.이것은 파일 시스템에 접속하여 조사하지 않으면 불가능합니다.

나는 항상 스스로에 대해 말할 필요 없이 스스로 발견할 수 있는 코드를 좋아하지만, 안타깝게도 이것도 효과가 있다.

다시 한 번 고마워!

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

이것은 어려운 문제이기 때문에 정적 분석을 사용하여 이 정보를 찾아야 합니다.실행 시에는 쉽게 이용할 수 없습니다.기본적으로 앱의 클래스 경로를 가져오고 사용 가능한 클래스를 스캔하여 해당 클래스가 상속되는 클래스의 바이트 코드 정보를 읽습니다.클래스 Dog는 Animal에서 직접 상속받지 않고 Animal에서 상속받는 Pet에서 상속받을 수 있으므로 해당 계층을 추적해야 합니다.

한 가지 방법은 클래스에서 정적 이니셜라이저를 사용하도록 하는 것입니다.다음 항목은 상속되지 않은 것 같습니다(상속되어 있으면 작동하지 않습니다).

public class Dog extends Animal{

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

관련된 모든 클래스에 이 코드를 추가해야 합니다.하지만 어딘가에 커다란 추악한 루프가 생기는 것을 피해서 모든 반에서 애니멀의 아이들을 찾아보는 테스트를 합니다.

패키지 레벨 주석을 사용하여 이 문제를 꽤 우아하게 해결했습니다.그리고 그 주석을 인수로 클래스 목록으로 만들었습니다.

인터페이스를 구현하는 Java 클래스 찾기

구현은 package-info.java를 만들고 지원하는 클래스 목록과 함께 매직주석을 넣기만 하면 됩니다.

직접 사용하고 있기 때문에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.
});

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

반응형