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


147

나는 이런 식으로하고 싶다 :

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 #에서는 이것이 사소한 것 같습니다 .


1
잠시 동안 검색 한 후에는 Java에서 깨지기 어려운 너트 인 것 같습니다. 여기에 몇 가지 정보가있는 스레드가 있습니다 : forums.sun.com/thread.jspa?threadID=341935&start=15 구현이 필요한 것 이상입니다. 지금 두 번째 구현을 고수 할 것입니다.
JohnnyLambada

답으로 독자들이 투표하도록
하세요

3
C # 에서이 작업을 수행 할 수있는 링크는 특별한 것을 보여주지 않습니다. AC # 어셈블리는 Java JAR 파일과 같습니다. 컴파일 된 클래스 파일 (및 리소스)의 모음입니다. 링크는 단일 어셈블리에서 클래스를 가져 오는 방법을 보여줍니다. Java로 거의 쉽게 할 수 있습니다. 문제는 모든 JAR 파일과 실제 느슨한 파일 (디렉토리)을 살펴 봐야한다는 것입니다. .NET과 동일한 작업을 수행해야합니다 (일종 또는 다양한 종류의 PATH 검색).
케빈 브록

답변:


156

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

6
org.reflections에 대한 링크를 주셔서 감사합니다. 나는 이것이 .Net 세계에서와 같이 쉬운 일이라고 잘못 생각 했으며이 답변으로 많은 시간을 절약했습니다.
akmad 2009 년

1
훌륭한 패키지 "org.reflections"에 대한 유용한 링크를 제공해 주셔서 감사합니다! 이를 통해 마침내 내 문제에 대한 실용적이고 깔끔한 해결책을 찾았습니다.
Hartmut P.

3
특정 패키지를 주목하지 않고 사용 가능한 모든 클래스를로드하는 등 모든 반영을 얻는 방법이 있습니까?
Joey Baruch

클래스를 찾는다고해서 인스턴스화 문제를 해결할 수는 없다는 점을 기억하십시오 ( animals.add( new c() );코드의 5 행 ) Class.newInstance(). 특정 클래스에는 매개 변수가없는 생성자가 있다는 보장이 없기 때문에 (C #을 사용하면 일반에서 요구할 수 있음)
usr-local-ΕΨΗΕΛΩΝ

ClassGraph : github.com/classgraph/classgraph 도 참조하십시오 (면책 조항, 저자입니다). 별도의 답변으로 코드 예제를 제공합니다.
Luke Hutchison

34

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

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


1
클래스의 주석을 기반으로 META-INF 서비스 파일을 쉽게 생성하려면 metainf-services.kohsuke.org 또는 code.google.com/p/spi
elek

브리아. 복잡한 수준을 추가하지만 클래스를 만든 다음 멀리 떨어진 토지에 등록 할 필요가 없습니다.
케빈 클라인

26 upvotes, 훨씬 더 나은 아래 답변을 참조하십시오
SobiborTreblinka

4
실제로 일종의 작업 솔루션이 필요한 경우 Reflections / Scannotations가 좋은 옵션이지만 외부 종속성을 부과하고 결정적이지 않으며 Java 커뮤니티 프로세스에서 지원하지 않습니다. 또한, 언제든지 얇은 공기에서 새로운 클래스를 만드는 것이 쉽지 않기 때문에 인터페이스를 구현하는 모든 클래스를 절대 신뢰할 수 없다는 점을 강조하고 싶었습니다. 모든 사마귀에 대해 Java의 서비스 로더는 이해하고 사용하기 쉽습니다. 나는 다른 이유로 그것을 멀리하고 싶지만 클래스 패스 스캐닝은 훨씬 더 나쁩니다 : stackoverflow.com/a/7237152/18187
ddimitrov

11

측면 지향적 인 관점에서 이것을 생각하십시오. 실제로하고 싶은 것은 런타임에 Animal 클래스를 확장 한 모든 클래스를 아는 것입니다. (제목보다 문제에 대해 약간 더 정확한 설명이라고 생각합니다. 그렇지 않으면 런타임 질문이 없다고 생각합니다.)

그래서 당신이 생각하는 것은 인스턴스화되고있는 현재 클래스의 유형을 정적 배열에 추가하는 기본 클래스 (Animal) 생성자를 만드는 것입니다.

대략적으로;

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 클래스 목록에 없습니다.


6

불행히도 이것은 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 응용 프로그램에서만 작동하며 응용 프로그램 서버에서는 작동하지 않는다는 의견이 정확합니다.


웹이나 J2EE 컨테이너와 같은 다른 방법으로 클래스를로드 할 때는 제대로 작동하지 않습니다.
johnstok

URL[]ClassLoader 계층에서 경로를 쿼리 한 경우 (종종 가능하지 않을 수도 있지만) 컨테이너에서라도 더 나은 작업을 수행 할 수 있습니다. 일반적으로 SecurityManagerJVM 내에로드 되기 때문에 권한이 있어야하는 경우가 종종 있습니다 .
케빈 브록

나를 위해 매력처럼 일했다! 나는 이것이 너무 무겁고 느리며 클래스 패스의 모든 클래스를 거치는 것을 두려워했지만 실제로 확인한 파일을 "-ejb-"또는 기타 인식 가능한 파일 이름을 포함하는 파일로 제한했으며 시작보다 100 배 빠릅니다. 임베디드 glassfish 또는 EJBContainer 특정 상황에서 "Stateless", "Stateful"및 "Singleton"주석을 찾는 클래스를 분석 한 후 JNDI 매핑 된 이름을 MockInitialContextFactory에 추가했습니다. 다시 감사합니다!
cs94njw

4

기존 코드를 리팩토링하지 않고 간단하고 빠른 것이 필요한 경우 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);

4

주어진 클래스의 모든 서브 클래스를 나열하는 가장 강력한 메커니즘은 현재 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);
}

그 결과 유형 목록 <클래스 <동물 >>입니다
hiaclibe

2

Java는 동적으로 클래스를로드하므로 클래스의 유니버스는 이미로드되었지만 아직 언로드되지 않은 클래스입니다. 아마도로드 된 각 클래스의 수퍼 타입을 확인할 수있는 사용자 정의 클래스 로더로 무언가를 수행 할 수 있습니다. 로드 된 클래스 세트를 쿼리하는 API가 없다고 생각합니다.


2

이것을 사용하십시오

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) 라이브러리 : Stripes

2
ResolverUtil에 대해 조금 더 이야기해야합니다. 무엇입니까? 어떤 도서관에서 왔습니까?
JohnnyLambada

1

이 질문에 답변 한 모든 분들께 감사드립니다.

이것은 실제로 깨지기 힘든 너트 인 것 같습니다. 나는 내 기본 클래스에서 정적 배열과 게터를 포기하고 만들었습니다.

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 파일 모음이기 때문에 런타임이 참조 될 때까지 클래스에 대해 알지 못한다고 생각합니다. 그 당시 로더가로드합니다. 내가하려고하는 것은 파일 시스템으로 이동하여 보지 않고 불가능한 것을 참조하기 전에 그것을 발견하는 것입니다.

나는 항상 자신에 대해 이야기하는 대신 스스로 발견 할 수있는 코드를 좋아하지만 아쉽게도 작동합니다.

다시 감사합니다!


1
Java는 자체 검색을 위해 설정되었습니다. 조금만 더 노력하면됩니다. 자세한 내용은 내 답변을 참조하십시오.
Dave Jarvis

1

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

0

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


0

한 가지 방법은 클래스가 정적 이니셜 라이저를 사용하도록하는 것입니다 ... 상속 된 것으로 생각하지 않습니다 (만약 그것이 작동하지 않을 것입니다).

public class Dog extends Animal{

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

이 코드를 관련된 모든 클래스에 추가해야합니다. 그러나 어딘가에 큰 못생긴 고리가 생기지 않고 Animal의 어린이를 검색하는 모든 클래스를 테스트합니다.


3
내 경우에는 작동하지 않습니다. 클래스는 어디에서나 참조되지 않으므로로드되지 않으므로 정적 초기화 프로그램이 호출되지 않습니다. 정적 이니셜 라이저는 클래스가로드 될 때 다른 모든 것보다 먼저 호출되는 것처럼 보이지만 제 경우에는 클래스가로드되지 않습니다.
JohnnyLambada

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.