Java에서 프로그래밍 방식으로 인터페이스의 모든 구현 목록을 얻으려면 어떻게해야합니까? [닫은]


81

반사 나 그런 식으로 할 수 있습니까?


마술 Eclipse의 바로 가기로 할 수 있습니까? ?
Tomasz Waszczyk

4
답을 선택해주세요.
Please_Dont_Bully_Me_SO_Lords

답변:


58

나는 잠시 동안 찾고 있었고 다른 접근 방식이있는 것 같습니다. 여기에 요약이 있습니다.

  1. 리플렉션 라이브러리는 종속성을 추가해도 괜찮다면 꽤 인기가 있습니다. 다음과 같이 표시됩니다.

    Reflections reflections = new Reflections("firstdeveloper.examples.reflections");
    Set<Class<? extends Pet>> classes = reflections.getSubTypesOf(Pet.class);
    
  2. ServiceLoader (erickson 답변에 따라) 그리고 다음과 같습니다.

    ServiceLoader<Pet> loader = ServiceLoader.load(Pet.class);
    for (Pet implClass : loader) {
        System.out.println(implClass.getClass().getSimpleName()); // prints Dog, Cat
    }
    

    이 작업을 수행하려면 PetSPI (ServiceProviderInterface) 로 정의 하고 해당 구현을 선언해야합니다. resources/META-INF/services이름 으로 파일을 만들고 그 안에 examples.reflections.Pet모든 구현을 선언 Pet하면됩니다.

    examples.reflections.Dog
    examples.reflections.Cat
    
  3. 패키지 수준 주석 . 다음은 예입니다.

    Package[] packages = Package.getPackages();
    for (Package p : packages) {
        MyPackageAnnotation annotation = p.getAnnotation(MyPackageAnnotation.class);
        if (annotation != null) {
            Class<?>[]  implementations = annotation.implementationsOfPet();
            for (Class<?> impl : implementations) {
                System.out.println(impl.getSimpleName());
            }
        }
    }
    

    및 주석 정의 :

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.PACKAGE)
    public @interface MyPackageAnnotation {
        Class<?>[] implementationsOfPet() default {};
    }
    

    패키지 package-info.java내부의 이름이 지정된 파일에서 패키지 수준 주석을 선언해야합니다 . 다음은 샘플 내용입니다.

    @MyPackageAnnotation(implementationsOfPet = {Dog.class, Cat.class})
    package examples.reflections;
    

    그 당시 ClassLoader에 알려진 패키지 만를 호출하여로드됩니다 Package.getPackages().

또한 디렉토리 기반 검색을 수행하지 않는 한 항상 이미로드 된 클래스로 제한되는 URLClassLoader 기반의 다른 접근 방식이 있습니다.


세 가지 접근 방식 중 어느 것이 더 효율적이고 가장 빠릅니까?
carlspring

@carlspring 나는 그들의 상대적 효율성에 대해 절대적인 진술을 할 수는 없지만 첫 번째 대안 (반사 라이브러리)은 나를 위해 꽤 잘 작동했습니다.
Ahmad Abdelghany

JDBC의 DriverManager는 어떻게 작동합니까? 비슷한 일을하지 않습니까 (클래스 경로에서 모든 드라이버 인터페이스 구현 검색)?
Alex Semeniuk

1
@AlexSemeniuk 이제 "DriverManager 메소드 getConnection 및 getDrivers가 Java Standard Edition Service Provider 메커니즘을 지원하도록 향상되었습니다."라는 문서에 따라 서비스 로더 / 제공자 메커니즘 (위의 접근 방식 # 2)을 지원한다고 생각합니다. 참조 docs.oracle.com/javase/8/docs/api/java/sql/DriverManager.html
아마드 Abdelghany

1
서비스 공급자 메커니즘이 Java 9를 사용하여 모듈 시스템에 통합되어 있으므로 패키지 수준 주석보다 훨씬 편리합니다. 즉, 고유 한 주석 유형을 만들 필요가 없으며 모듈에서 구현을 선언 할 수 있습니다. -정보는 모듈 식 소프트웨어를 작성할 때 작성하고 구현의 유효성에 관한 컴파일 타임 피드백을받을 수 있습니다.
Holger

28

erickson이 말한 것, 그러나 여전히하고 싶다면 Reflections를 살펴보십시오 . 페이지에서 :

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

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

12
더 구체적으로 :new Reflections("my.package").getSubTypesOf(MyInterface.class)
ZAPP

27

일반적으로 이렇게하는 데 비용이 많이 듭니다. 리플렉션을 사용하려면 클래스를로드해야합니다. 클래스 경로에서 사용 가능한 모든 클래스를로드하려면 시간과 메모리가 필요하므로 권장되지 않습니다.

이것을 피하려면 리플렉션 대신 더 효율적으로 작동하는 자체 클래스 파일 파서를 구현해야합니다. 바이트 코드 엔지니어링 라이브러리가이 접근 방식에 도움이 될 수 있습니다.

서비스 제공자 메커니즘은 플러그 서비스의 구현을 열거하는 기존의 방법이며, 자바 9. 프로젝트 직소 (모듈)의 도입으로 더 설립되었다 ServiceLoader자바 6 또는 이전 버전에서 자신을 구현합니다. 다른 답변에 를 제공 했습니다 .


4
안타깝게도 서비스 공급자 메커니즘을 사용하려면 종종 실행 불가능한 별도의 파일에 관심 목록에있을 수있는 클래스를 나열해야합니다.
averasko

4
인터페이스를 구현하는 소스 코드를 작성하고 컴파일하고 배포하는 것은 가능하지만 구현의 FQN과 함께 텍스트 파일을 포함하는 것은 불가능합니까? 그럴 경우는 드뭅니다. 거의 "자주".
erickson

JDBC의 DriverManager는 어떻게 작동합니까? 비슷한 일을하지 않습니까 (클래스 경로에서 모든 드라이버 인터페이스 구현 검색)?
Alex Semeniuk

1
@AlexSemeniuk 아니요. 위에서 설명한 서비스 로더 메커니즘을 사용합니다. JDBC 4.0+ 드라이버는 이름을 다음 위치에 나열해야합니다.META-INF/services/java.sql.Driver
erickson

16

Spring에는 이것을 달성하는 매우 간단한 방법이 있습니다.

public interface ITask {
    void doStuff();
}

@Component
public class MyTask implements ITask {
   public void doStuff(){}
}

그런 다음 유형 목록을 자동으로 연결할 수 ITask있으며 Spring은 모든 구현으로이를 채울 것입니다.

@Service
public class TaskService {

    @Autowired
    private List<ITask> tasks;
}

4
정확히는 아니지만 Spring은 ITask 유형의 모든 을 채울 것입니다 .
Olivier Gérardin

5

주어진 인터페이스를 구현하는 모든 클래스를 나열하는 가장 강력한 메커니즘은 현재 ClassGraph 입니다. 새로운 JPMS 모듈 시스템을 포함하여 가능한 가장 광범위한 클래스 경로 사양 메커니즘을 처리하기 때문 입니다. (저는 저자입니다.)

try (ScanResult scanResult = new ClassGraph().whitelistPackages("x.y.z")
        .enableClassInfo().scan()) {
    for (ClassInfo ci : scanResult.getClassesImplementing("x.y.z.SomeInterface")) {
        foundImplementingClass(ci);  // Do something with the ClassInfo object
    }
}

1
샴처럼 ClassGraph 라이브러리 작품은, 당신에게 @Luke 허치슨 감사합니다
릴로 Semenko

4

에릭슨이 말한 것이 가장 좋습니다. 다음은 관련 질문 및 답변 스레드입니다-http: //www.velocityreviews.com/forums/t137693-find-all-implementing-classes-in-classpath.html

Apache BCEL 라이브러리를 사용하면 클래스를로드하지 않고도 읽을 수 있습니다. 확인 단계를 건너 뛸 수 있기 때문에 더 빨라질 것이라고 생각합니다. 클래스 로더를 사용하여 모든 클래스를로드 할 때의 또 다른 문제는 막대한 메모리 영향을받을뿐만 아니라 원하지 않는 정적 코드 블록을 실수로 실행한다는 것입니다.

아파치 BCEL 라이브러리 링크 - http://jakarta.apache.org/bcel/


4

ClassGraph를 사용하면 매우 간단합니다.

구현을 찾는 Groovy 코드 my.package.MyInterface:

@Grab('io.github.classgraph:classgraph:4.6.18')
import io.github.classgraph.*
new ClassGraph().enableClassInfo().scan().withCloseable { scanResult ->
    scanResult.getClassesImplementing('my.package.MyInterface').findAll{!it.abstract}*.name
}

당신은 호출 할 필요가 scan().withCloseable { ... }: 그루비에서, 또는 사용 시도 -과 - 자원 자바 github.com/classgraph/classgraph/wiki/...는 또한, 마지막 부분은해야 .name하지 .className때문에, .getName()클래스의 이름을 얻을 수있는 올바른 방법이다 A로부터 ClassInfo객체입니다.
누가 복음 허치슨


2

@ kaybee99의 답변의 새 버전이지만 이제 사용자가 묻는 내용을 반환합니다.

Spring에는 이것을 달성하는 매우 간단한 방법이 있습니다.

public interface ITask {
    void doStuff();
    default ITask getImplementation() {
       return this;
    }

}

@Component
public class MyTask implements ITask {
   public void doStuff(){}
}

그런 다음 유형 목록을 자동으로 연결할 수 ITask있으며 Spring은 모든 구현으로이를 채울 것입니다.

@Service
public class TaskService {

    @Autowired(required = false)
    private List<ITask> tasks;

    if ( tasks != null)
    for (ITask<?> taskImpl: tasks) {
        taskImpl.doStuff();
    }   
}

1

또한 IDE 플러그인을 작성하는 경우 (하려는 작업이 비교적 일반적인 경우) IDE는 일반적으로 사용자 코드의 현재 상태에 대한 클래스 계층 구조에 액세스하는보다 효율적인 방법을 제공합니다.


1

나는 같은 문제에 부딪쳤다. 내 솔루션은 리플렉션을 사용하여 ObjectFactory 클래스의 모든 메서드를 검사하여 바인딩 된 POJO 중 하나의 인스턴스를 반환하는 createXXX () 메서드가 아닌 메서드를 제거하는 것입니다. 이렇게 발견 된 각 클래스는 Class [] 배열에 추가되고 JAXBContext 인스턴스화 호출에 전달됩니다. 어쨌든 필요했던 ObjectFactory 클래스를로드하기 만하면됩니다. 저는 ObjectFactory 클래스 만 유지하면됩니다. 작업은 수작업으로 수행하거나 (제 경우에는 POJO로 시작하고 schemagen을 사용했기 때문에) xjc에서 필요에 따라 생성 할 수 있습니다. 어느 쪽이든 성능이 뛰어나고 간단하며 효과적입니다.

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