Java에서 주어진 클래스의 모든 서브 클래스를 어떻게 찾습니까?


207

Java에서 주어진 클래스의 모든 서브 클래스 (또는 주어진 인터페이스의 모든 구현 자)를 찾으려고 어떻게합니까? 현재로서는이 작업을 수행 할 수있는 방법이 있지만 매우 비효율적입니다 (최소하게 말하면). 방법은 다음과 같습니다

  1. 클래스 경로에 존재하는 모든 클래스 이름 목록을 가져옵니다.
  2. 각 클래스를로드하고 테스트하여 해당 클래스 또는 인터페이스의 서브 클래스 또는 구현 자인지 확인하십시오.

이클립스에는 Type Hierarchy라는 멋진 기능이 있는데이를 효율적으로 보여줍니다. 프로그래밍 방식으로 어떻게 진행합니까?


1
Reflections 및 Spring을 기반으로 한 솔루션이 흥미로워 보이지만 의존성이없는 간단한 솔루션이 필요했습니다. 내 원래 코드 (일부 조정)가 갈 길이었던 것 같습니다.
Avrom

1
getSupeClass 메소드를 재귀 적으로 사용할 수 있습니까?

특정 클래스모든 하위 클래스 를 구체적으로 찾고있었습니다 . getSuperClass는 클래스에 어떤 서브 클래스가 있는지 알려주지 않고 특정 서브 클래스에 대한 즉각적인 수퍼 클래스 만 가져옵니다. 또한 클래스의 isAssignableFrom 메소드는 제안한 내용에 더 적합합니다 (재귀 필요 없음).
Avrom

이 질문은 다른 많은 복제본과 연결되어 있지만 유용한 일반 Java 답변은 포함되어 있지 않습니다. 한숨 ..
Eric Duminil

답변:


77

설명 한 것 이외의 다른 방법은 없습니다. 생각해보십시오-클래스 패스에서 각 클래스를 스캔하지 않고 클래스가 어떤 클래스를 클래스 X로 확장하는지 알 수 있습니까?

Eclipse는 "유형 계층에 표시"단추를 누른 시점에 이미 모든 유형 데이터가로드되어 있기 때문에 "효율적인"시간으로 보이는 수퍼 클래스와 서브 클래스에 대해서만 알려줄 수 있습니다. 지속적으로 클래스를 컴파일하고 클래스 패스의 모든 것에 대해 알고 있습니다.)


23
org.reflections 라는 간단한 라이브러리가 이제 이것과 다른 일반적인 리플렉션 작업을 도와줍니다. 이 라이브러리를 사용하면 reflections.getSubTypesOf(aClazz)) 링크를
Enwired

@matt b-모든 클래스를 스캔 해야하는 경우 프로젝트에 클래스가 많을 때 클래스가 거의 하위 클래스 인 경우에도 성능 저하가 있음을 의미합니까?
LeTex

바로 그거죠. 그것은 단지 모든 수업에 영향을 미칩니다. 클래스가 확장되지 않을 것으로 알고있는 특정 패키지를 제외하거나 클래스 파일을 열고 리플렉션 스캐너가 읽지 않도록 클래스의 상수 섹션에서 클래스 이름을 확인하는 등의 속도를 낼 수 있습니다. (직접) 수퍼 클래스에 대한 필수 참조가 포함되지 않은 클래스에 대한 추가 정보 간접적으로 추가 스캔이 필요합니다. 현재로서는 최고입니다.
Martin Kersten

fforw의 답변은 저에게 효과적이며 정답으로 표시해야합니다. 분명히 이것은 클래스 경로 스캔으로 가능합니다.
Farrukh Najmi

당신은 Burningwave 라이브러리에 대한 아래의 응답있는 거 잘못 참조

127

순수 Java를 사용하면 클래스 스캔이 쉽지 않습니다.

스프링 프레임 워크는 필요한 것을 수행 할 수있는 ClassPathScanningCandidateComponentProvider 클래스를 제공 합니다. 다음 예제는 org.example.package 패키지에서 MyClass의 모든 서브 클래스를 찾습니다.

ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
provider.addIncludeFilter(new AssignableTypeFilter(MyClass.class));

// scan in org.example.package
Set<BeanDefinition> components = provider.findCandidateComponents("org/example/package");
for (BeanDefinition component : components)
{
    Class cls = Class.forName(component.getBeanClassName());
    // use class cls found
}

이 방법은 후보를 찾기 위해 바이트 코드 분석기를 사용하는 추가 이점이 있습니다. 즉 , 스캔하는 모든 클래스를로드 하지는 않습니다 .


23
기본 필터를 사용하지 않으려면 ClassPathScanningCandidateComponentProvider를 작성할 때 매개 변수로 False를 전달해야합니다. 기본 필터는 다른 유형의 클래스 (예 : @Component로 주석이 달린 클래스)와 일치합니다. 여기서는 AssignableTypeFilter 만 활성화하기를 원합니다.
MCDS

당신은 쉽지 않다고 말하지만, 순수한 자바로하고 싶다면 어떻게해야할까요?
Aequitas

49

내장 Java Reflections API 만 사용하는 것은 불가능합니다.

이 정보에 액세스 할 수 있도록 클래스 경로의 필요한 스캔 및 인덱싱을 수행하는 프로젝트가 있습니다 ...

반사

Scannotations의 정신에 따른 Java 런타임 메타 데이터 분석

Reflections는 클래스 경로를 스캔하고 메타 데이터를 색인화하며 런타임시이를 쿼리하고 프로젝트 내의 많은 모듈에 대한 정보를 저장하고 수집 할 수 있습니다.

Reflections를 사용하여 메타 데이터를 쿼리하여 다음을 수행 할 수 있습니다.

  • 특정 유형의 모든 하위 유형을 가져옵니다
  • 주석으로 주석이 달린 모든 유형을 얻습니다.
  • 주석 매개 변수 일치를 포함하여 주석으로 주석이 달린 모든 유형을 가져옵니다.
  • 모든 메소드에 주석이 달린

(면책 조항 : 나는 그것을 사용하지 않았지만 프로젝트 설명은 귀하의 요구에 정확히 맞는 것 같습니다.)


1
흥미 롭군 이 프로젝트에는 문서에서 언급하지 않은 일부 의존성이있는 것으로 보입니다. 즉 (지금까지 찾은 것) : javaassist, log4J, XStream
Avrom

3
나는이 프로젝트를 maven에 포함 시켰으며 정상적으로 작동했습니다. 서브 클래스를 얻는 것은 실제로 첫 번째 소스 코드 예제이며 길이는 두 줄입니다 :-)
KarlsFriend

내장 Java Reflections API 만 사용하거나 그렇게하는 것이 매우 불편한가요?
흐름

1
Reflections를 사용하고 GlassFish에 앱 WAR을 배포 할 때는주의하십시오! Guava 라이브러리에 충돌이 있으며 오류 CDI 배포 실패 : WELD-001408 과 함께 배포가 실패 합니다. 자세한 내용 은 GLASSFISH- 20579 를 참조 하십시오. 이 경우 FastClasspathScanner 가 솔루션입니다.
lu_ko

나는이 프로젝트를 시도하고 작동합니다. 방금 전략 디자인 패턴을 향상시키고 데모를 공유 할 모든 전략 구체적 클래스 (하위 클래스)를 얻는 데 사용합니다.
Xin Meng

10

클래스에 대해 생성 된 Javadoc 에는 알려진 서브 클래스 (및 인터페이스, 알려진 구현 클래스)의 목록이 포함 된다는 것을 잊지 마십시오 .


3
이것은 완전히 잘못되었습니다. 슈퍼 클래스는 javadoc 또는 주석에서도 하위 클래스에 의존해서는 안됩니다.
헌터

@hunter 동의하지 않습니다. JavaDoc에 알려진 서브 클래스 목록이 포함되어있는 것이 완전히 맞습니다 . 물론, "알려진"은 찾고자하는 수업을 포함하지 않을 수도 있지만, 일부 사용 사례로는 충분할 것입니다.
Qw3ry

그리고 어떤 경우에도 일부 클래스를 놓칠 수 있습니다. 새 경로를 클래스 경로에 (런타임 동안)로드 할 수 있으며 이전에 발생한 모든 감지가 실패합니다.
Qw3ry

10

ClassGraph 사용해보십시오 . (면책 조항, 저자입니다). ClassGraph는 주어진 클래스의 서브 클래스, 런타임 또는 빌드 타임에 대한 스캔을 지원합니다. ClassGraph는 메모리, 클래스 경로의 모든 클래스 또는 허용 된 패키지의 클래스에 대한 전체 클래스 그래프 (모든 클래스, 주석, 메소드, 메소드 매개 변수 및 필드)의 추상 표현을 빌드 할 수 있지만 해당 클래스 그래프를 조회 할 수 있습니다. 당신이 원합니다. ClassGraph는 다른 스캐너보다 더 많은 클래스 경로 지정 메커니즘과 클래스 로더를 지원 하며 새로운 JPMS 모듈 시스템과 완벽하게 작동하므로 ClassGraph를 기반으로 코드를 작성하면 코드를 최대한 이식 할 수 있습니다. 여기 API를 참조하십시오.


9

나는 몇 년 전에 이것을했습니다. 이를 수행하는 가장 신뢰할 수있는 방법 (예 : 공식 Java API 및 외부 종속성 없음)은 사용자 정의 doclet을 작성하여 런타임시 읽을 수있는 목록을 작성하는 것입니다.

다음과 같이 명령 행에서 실행할 수 있습니다.

javadoc -d build -doclet com.example.ObjectListDoclet -sourcepath java/src -subpackages com.example

또는 다음과 같이 개미에서 실행하십시오.

<javadoc sourcepath="${src}" packagenames="*" >
  <doclet name="com.example.ObjectListDoclet" path="${build}"/>
</javadoc>

기본 코드는 다음과 같습니다.

public final class ObjectListDoclet {
    public static final String TOP_CLASS_NAME =  "com.example.MyClass";        

    /** Doclet entry point. */
    public static boolean start(RootDoc root) throws Exception {
        try {
            ClassDoc topClassDoc = root.classNamed(TOP_CLASS_NAME);
            for (ClassDoc classDoc : root.classes()) {
                if (classDoc.subclassOf(topClassDoc)) {
                    System.out.println(classDoc);
                }
            }
            return true;
        }
        catch (Exception ex) {
            ex.printStackTrace();
            return false;
        }
    }
}

간단히하기 위해 명령 줄 인수 구문 분석을 제거하고 파일이 아닌 System.out에 작성하고 있습니다.


프로그래밍 방식으로이를 사용하는 것은 까다로울 수 있지만 똑똑한 접근 방식입니다.
Janaka Bandara

8

나는이 파티에 몇 년 늦었다는 것을 알고 있지만, 같은 문제를 해결하려고이 질문을 겪었습니다. Eclipse 플러그인을 작성하고 (따라서 캐싱 등을 활용하는 경우) 프로그래밍 방식으로 Eclipse의 내부 검색을 사용하여 인터페이스를 구현하는 클래스를 찾을 수 있습니다. 내 (매우 거친) 첫 컷은 다음과 같습니다.

  protected void listImplementingClasses( String iface ) throws CoreException
  {
    final IJavaProject project = <get your project here>;
    try
    {
      final IType ifaceType = project.findType( iface );
      final SearchPattern ifacePattern = SearchPattern.createPattern( ifaceType, IJavaSearchConstants.IMPLEMENTORS );
      final IJavaSearchScope scope = SearchEngine.createWorkspaceScope();
      final SearchEngine searchEngine = new SearchEngine();
      final LinkedList<SearchMatch> results = new LinkedList<SearchMatch>();
      searchEngine.search( ifacePattern, 
      new SearchParticipant[]{ SearchEngine.getDefaultSearchParticipant() }, scope, new SearchRequestor() {

        @Override
        public void acceptSearchMatch( SearchMatch match ) throws CoreException
        {
          results.add( match );
        }

      }, new IProgressMonitor() {

        @Override
        public void beginTask( String name, int totalWork )
        {
        }

        @Override
        public void done()
        {
          System.out.println( results );
        }

        @Override
        public void internalWorked( double work )
        {
        }

        @Override
        public boolean isCanceled()
        {
          return false;
        }

        @Override
        public void setCanceled( boolean value )
        {
        }

        @Override
        public void setTaskName( String name )
        {
        }

        @Override
        public void subTask( String name )
        {
        }

        @Override
        public void worked( int work )
        {
        }

      });

    } catch( JavaModelException e )
    {
      e.printStackTrace();
    }
  }

지금까지 본 첫 번째 문제는 모든 하위 클래스가 아닌 인터페이스를 직접 구현하는 클래스 만 잡는다는 것입니다.


3
또는 직접 검색하지 않아도됩니다. dev.eclipse.org/newslists/news.eclipse.tools.jdt/msg05036.html
Curtis

7

다른 답변에 언급 된 제한 사항을 염두에두고 다음과 같은 방식으로 openpojoPojoClassFactory ( Maven 에서 사용 가능 )를 사용할 수도 있습니다 .

for(PojoClass pojoClass : PojoClassFactory.enumerateClassesByExtendingType(packageRoot, Superclass.class, null)) {
    System.out.println(pojoClass.getClazz());
}

packageRoot검색하려는 패키지의 루트 문자열은 어디에 있고 (예 : "com.mycompany"그냥 "com"), Superclass슈퍼 타입입니다 (이것은 인터페이스에서도 작동합니다).


지금까지 제안 된 것 중 가장 빠르고 가장 우아한 솔루션입니다.
KidCrippler


4

특정 요구 사항에 따라 경우에 따라 Java의 서비스 로더 메커니즘으로 원하는 결과를 얻을 수 있습니다.

즉, 개발자는 클래스가 JAR / WAR 파일의 META-INF/services디렉토리 에있는 파일에 나열하여 클래스가 다른 클래스를 서브 클래 싱하거나 인터페이스를 구현 함을 명시 적으로 선언 할 수 있습니다 . 그런 다음 객체를 java.util.ServiceLoader제공 할 때 해당 Class클래스의 선언 된 모든 서브 클래스의 인스턴스를 생성 하는 클래스를 사용하여 발견 할 수 있습니다 .Class 인터페이스를 나타내는 해당 인터페이스를 구현하는 모든 .

이 접근법의 주요 장점은 서브 클래스에 대해 전체 클래스 경로를 수동으로 스캔 할 필요가 없다는 것입니다. 모든 발견 논리가 ServiceLoader클래스 내에 포함되어 있으며 클래스에 명시 적으로 선언 된 클래스 만로드합니다.META-INF/services 디렉토리에 경로의 모든 클래스는 아님) .

그러나 몇 가지 단점이 있습니다.

  • 모든 서브 클래스를 찾을 수 없으며 명시 적으로 선언 된 서브 클래스 만 찾을 수 있습니다. 따라서 모든 서브 클래스를 실제로 찾아야하는 경우이 방법으로는 충분하지 않을 수 있습니다.
  • 개발자는 META-INF/services디렉토리 아래에 클래스를 명시 적으로 선언해야합니다 . 이는 개발자에게 추가적인 부담이며 오류가 발생하기 쉽습니다.
  • ServiceLoader.iterator()서브 클래스 인스턴스가 아니라 자신의 생성 Class객체. 이로 인해 두 가지 문제가 발생합니다.
    • 서브 클래스가 어떻게 구성되는지에 대해서는 아무 말도하지 않습니다. 인수를 생성하지 않는 생성자가 인스턴스를 만드는 데 사용됩니다.
    • 따라서 서브 클래스에는 기본 생성자가 있거나 인수가없는 생성자를 명시 적으로 선언해야합니다.

분명히 Java 9는 이러한 단점 중 일부 (특히 서브 클래스 인스턴스화 관련 문제)를 해결합니다.

인터페이스를 구현하는 클래스를 찾는 데 관심이 있다고 가정하십시오 com.example.Example.

package com.example;

public interface Example {
    public String getStr();
}

클래스 com.example.ExampleImpl는 해당 인터페이스를 구현합니다.

package com.example;

public class ExampleImpl implements Example {
    public String getStr() {
        return "ExampleImpl's string.";
    }
}

텍스트가 포함 된 파일을 만들어 클래스 ExampleImpl를 구현 한 Example것으로 선언합니다 .META-INF/services/com.example.Examplecom.example.ExampleImpl

그런 다음 다음과 같이 Example(의 인스턴스를 포함하여) 각 구현의 인스턴스를 얻을 수 있습니다 ExampleImpl.

ServiceLoader<Example> loader = ServiceLoader.load(Example.class)
for (Example example : loader) {
    System.out.println(example.getStr());
}

// Prints "ExampleImpl's string.", plus whatever is returned
// by other declared implementations of com.example.Example.

3

물론 이것은 현재 클래스 경로에 존재하는 모든 하위 클래스 만 찾을 것입니다. 아마도 이것은 당신이 현재보고있는 것에 OK이며, 당신은 이것을 고려했을 가능성이 있지만, 어떤 시점에서라도final 의를 야생으로 다면 (다양한 레벨의 "야생"), 그것은 완전히 실현 가능합니다. 다른 누군가가 당신이 알지 못하는 자신의 서브 클래스를 작성했습니다.

따라서 변경을 원하고 서브 클래스의 동작에 어떤 영향을 미치는지 확인하기 위해 모든 서브 클래스를보고자한다면 보이지 않는 서브 클래스를 염두에 두십시오. 이상적으로는 모든 비 개인용 메소드와 클래스 자체가 잘 문서화되어야합니다. 메소드 / 비 개인 필드의 시맨틱을 변경하지 않고이 문서에 따라 변경을 수행하십시오. 최소한 수퍼 클래스 정의를 따르는 서브 클래스에 대한 변경 사항은 이전 버전과 호환 가능해야합니다.


3

구현과 Eclipse가 다른 이유는 매번 스캔하는 반면 Eclipse (및 기타 도구)는 한 번만 (프로젝트로드 중 대부분의 시간 동안) 스캔하고 색인을 작성하기 때문입니다. 다음에 데이터를 요청할 때는 다시 검색하지 않지만 색인을 확인하십시오.


3

모든 하위 클래스에 대한 클래스 경로를 스캔하는 리플렉션 lib를 사용하고 있습니다 : https://github.com/ronmamo/reflections

이것이 수행되는 방법입니다.

Reflections reflections = new Reflections("my.project");
Set<Class<? extends SomeType>> subTypes = reflections.getSubTypesOf(SomeType.class);

2

부모 클래스 생성자 (this.getClass (). getName ()) 내부의 정적 맵에 추가하거나 기본 생성자를 생성하지만 런타임에 업데이트됩니다. 지연 초기화가 옵션이면이 방법을 시도해 볼 수 있습니다.



0

테스트 클래스로 새 클래스가 코드에 추가되었는지 확인하기 위해이 작업을 수행해야했습니다. 이것이 내가 한 일입니다

final static File rootFolder = new File(SuperClass.class.getProtectionDomain().getCodeSource().getLocation().getPath());
private static ArrayList<String> files = new ArrayList<String>();
listFilesForFolder(rootFolder); 

@Test(timeout = 1000)
public void testNumberOfSubclasses(){
    ArrayList<String> listSubclasses = new ArrayList<>(files);
    listSubclasses.removeIf(s -> !s.contains("Superclass.class"));
    for(String subclass : listSubclasses){
        System.out.println(subclass);
    }
    assertTrue("You did not create a new subclass!", listSubclasses.size() >1);     
}

public static void listFilesForFolder(final File folder) {
    for (final File fileEntry : folder.listFiles()) {
        if (fileEntry.isDirectory()) {
            listFilesForFolder(fileEntry);
        } else {
            files.add(fileEntry.getName().toString());
        }
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.