런타임시 Java 주석 스캔 [닫기]


253

주석이 달린 클래스의 전체 클래스 경로를 검색하는 가장 좋은 방법은 무엇입니까?

라이브러리를 만들고 있는데 사용자가 클래스에 주석을 달 수 있도록 웹 응용 프로그램이 시작될 때 특정 주석에 대해 전체 클래스 경로를 스캔해야합니다.

이 작업을 수행 할 라이브러리 또는 Java 기능을 알고 있습니까?

편집 : Java EE 5 웹 서비스 또는 EJB의 새로운 기능과 같은 것을 생각하고 있습니다. 당신은 당신의 클래스에 주석 @WebService또는 @EJB그들이 원격으로 액세스 할 수있는 로딩 그렇게하는 동안 시스템은 이러한 클래스를 찾습니다.

답변:


210

org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider를 사용하십시오.

API

기본 패키지에서 클래스 경로를 스캔하는 구성 요소 제공자. 그런 다음 후보를 찾기 위해 결과 클래스에 제외 및 포함 필터를 적용합니다.

ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(<DO_YOU_WANT_TO_USE_DEFALT_FILTER>);

scanner.addIncludeFilter(new AnnotationTypeFilter(<TYPE_YOUR_ANNOTATION_HERE>.class));

for (BeanDefinition bd : scanner.findCandidateComponents(<TYPE_YOUR_BASE_PACKAGE_HERE>))
    System.out.println(bd.getBeanClassName());

5
정보 주셔서 감사합니다. 필드에 사용자 정의 주석이있는 클래스의 클래스 경로를 스캔하는 방법도 알고 있습니까?
Javatar

6
@Javatar Java 리플렉션 API를 사용하십시오. <YOUR_CLASS> .class.getFields () 각 필드에 대해 getAnnotation (<YOUR_ANNOTATION>)을 호출하십시오.
Arthur Ronald

1
참고 : Spring 애플리케이션 에서이 작업을 수행하는 경우 Spring은 여전히 @Conditional주석을 기반으로 평가하고 작동 합니다. 따라서 클래스의 @Conditional값이 false를 반환 findCandidateComponents하면 스캐너의 필터와 일치하더라도 으로 반환되지 않습니다 . 이것은 오늘 나를 던졌습니다. 대신 Jonathan의 솔루션을 대신 사용했습니다.
Adam Burley

1
@ArthurRonald 죄송합니다, Arthur. 나는 BeanDefinition객체가 클래스를 직접 얻는 방법을 제공하지 않는다는 것을 의미합니다 . 가장 가까운 것은 getBeanClassName정규화 된 클래스 이름을 반환하는 것 같지만 이 메서드의 정확한 동작은 명확하지 않습니다. 또한 클래스가 어느 클래스 로더에 있는지 명확하지 않습니다.
Max

3
@Max 이것을 시도하십시오 : Class<?> cl = Class.forName(beanDef.getBeanClassName()); farenda.com/spring/find-annotated-classes
James Watkins

149

또 다른 해결책은 Google 반영입니다. 입니다.

빠른 검토:

  • Spring 솔루션은 Spring을 사용하는 경우 갈 수있는 방법입니다. 그렇지 않으면 큰 의존성입니다.
  • ASM을 직접 사용하는 것은 다소 번거 롭습니다.
  • Java Assist를 직접 사용하는 것도 복잡합니다.
  • Annovention은 매우 가볍고 편리합니다. 아직 maven 통합이 없습니다.
  • Google 리플렉션은 Google 컬렉션을 끌어냅니다. 모든 것을 색인화 한 다음 매우 빠릅니다.

43
new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class). cutest.
zapp

4
패키지 이름을 지정해야합니까? 와일드 카드? 클래스 패스의 모든 클래스는 무엇입니까?
Sunnyday


org.reflections 라이브러리는 java 13에서 작동하지 않습니다 (아마도). 처음으로 전화를 걸면 괜찮은 것 같습니다. 후속 인스턴스화 및 사용은 검색 URL이 구성되지 않았다고 말하지 않습니다.
Evvo

44

ClassGraph를 사용하여 주어진 주석이있는 클래스를 찾을 수있을 뿐만 아니라 다른 관심있는 기준 (예 : 주어진 인터페이스를 구현하는 클래스)을 검색 할 수 있습니다. (면책 조항, ClassGraph의 저자입니다.) ClassGraph는 전체 클래스 그래프 (모든 클래스, 주석, 메소드, 메소드 매개 변수 및 필드)를 메모리, 클래스 경로의 모든 클래스 또는 클래스의 추상 표현을 작성할 수 있습니다 허용 된 패키지를 사용하면 원하는 클래스 그래프를 쿼리 할 수 ​​있습니다. ClassGraph는 다른 스캐너보다 더 많은 클래스 경로 지정 메커니즘 및 클래스 로더를 지원 하며 새로운 JPMS 모듈 시스템과도 원활하게 작동하므로 ClassGraph를 기반으로 코드를 작성하면 코드를 최대한 이식 할 수 있습니다. 여기 API를 참조하십시오.


1
Java 8을 실행해야합니까?
David George

1
Java7을 사용하도록 업데이트되었으며 문제 없습니다. 주석을 제거하고 익명 내부 클래스를 사용하도록 함수를 변환하십시오. 나는 1 파일 스타일을 좋아한다. 코드는 깨끗하기 때문에 좋아하는 몇 가지 (클래스 + 주석)를 지원하지 않더라도 추가하기가 쉽다고 생각합니다. 훌륭한 일! 누군가 v7을 수정하기 위해 작업을 수행 할 수없는 경우 아마도로 이동해야합니다 Reflections. 또한 구아바 / etc를 사용하고 있고 파이처럼 쉽게 컬렉션을 변경하려는 경우. 내부에서도 좋은 의견.
Andrew Backer

2
@Alexandros 덕분에 ClassGraph를 확인해야합니다 .FastClasspathScanner보다 크게 향상되었습니다.
Luke Hutchison

2
@AndrewBacker ClassGraph (FastClasspathScanner의 새 버전)는 필터 또는 설정 작업을 통해 부울 연산을 완벽하게 지원합니다. 여기에 코드 예제를 참조하십시오 github.com/classgraph/classgraph/wiki/Code-examples
누가 복음 허치슨

1
@Luke Hutchison은 이미 ClassGraph를 사용하고 있습니다. Java 10으로 마이그레이션하는 데 도움이되었습니다. 정말 유용한 라이브러리입니다.
Alexandros


20

Java Pluggable Annotation Processing API를 사용하여 컴파일 프로세스 중에 실행될 주석 프로세서를 작성할 수 있으며 주석이 달린 모든 클래스를 수집하고 런타임에 사용할 색인 파일을 빌드합니다.

런타임에 클래스 경로를 스캔 할 필요가 없기 때문에 어노테이션이있는 클래스 발견을 수행 할 수있는 가장 빠른 방법입니다. 이는 일반적으로 매우 느린 조작입니다. 또한이 방법은 모든 클래스 로더에서 작동하며 일반적으로 런타임 스캐너에서 지원되는 URLClassLoader와 함께 작동합니다.

위의 메커니즘은 이미 ClassIndex 라이브러리 에서 구현되었습니다 .

이를 사용하려면 @IndexAnnotated 메타 주석으로 사용자 정의 주석에 주석을 달 수 있습니다 . 이렇게하면 컴파일시 색인 파일 인 META-INF / annotations / com / test / YourCustomAnnotation이 주석이 달린 모든 클래스를 나열합니다. 다음을 실행하여 런타임시 인덱스에 액세스 할 수 있습니다.

ClassIndex.getAnnotated(com.test.YourCustomAnnotation.class)

5

대답하기에 너무 늦었습니까? 나는 도서관에가는 것이 더 낫습니다.ClassPathScanningCandidateComponentProvider 또는 Scannotations 와 같은 좋습니다.

그러나 누군가 누군가 classLoader를 사용 해보고 싶더라도 패키지의 클래스에서 주석을 인쇄하기 위해 직접 작성했습니다.

public class ElementScanner {

public void scanElements(){
    try {
    //Get the package name from configuration file
    String packageName = readConfig();

    //Load the classLoader which loads this class.
    ClassLoader classLoader = getClass().getClassLoader();

    //Change the package structure to directory structure
    String packagePath  = packageName.replace('.', '/');
    URL urls = classLoader.getResource(packagePath);

    //Get all the class files in the specified URL Path.
    File folder = new File(urls.getPath());
    File[] classes = folder.listFiles();

    int size = classes.length;
    List<Class<?>> classList = new ArrayList<Class<?>>();

    for(int i=0;i<size;i++){
        int index = classes[i].getName().indexOf(".");
        String className = classes[i].getName().substring(0, index);
        String classNamePath = packageName+"."+className;
        Class<?> repoClass;
        repoClass = Class.forName(classNamePath);
        Annotation[] annotations = repoClass.getAnnotations();
        for(int j =0;j<annotations.length;j++){
            System.out.println("Annotation in class "+repoClass.getName()+ " is "+annotations[j].annotationType().getName());
        }
        classList.add(repoClass);
    }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

/**
 * Unmarshall the configuration file
 * @return
 */
public String readConfig(){
    try{
        URL url = getClass().getClassLoader().getResource("WEB-INF/config.xml");
        JAXBContext jContext = JAXBContext.newInstance(RepositoryConfig.class);
         Unmarshaller um =  jContext.createUnmarshaller();
         RepositoryConfig rc = (RepositoryConfig) um.unmarshal(new File(url.getFile()));
         return rc.getRepository().getPackageName();
        }catch(Exception e){
            e.printStackTrace();
        }
    return null;

}
}

그리고 설정 파일에서 패키지 이름을 넣고 클래스에 정렬 해제하십시오.


3

Spring에서는 AnnotationUtils 클래스를 사용하여 다음을 작성할 수도 있습니다. 즉 :

Class<?> clazz = AnnotationUtils.findAnnotationDeclaringClass(Target.class, null);

자세한 내용과 다른 모든 방법은 공식 문서를 확인하십시오 : https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/annotation/AnnotationUtils.html


4
좋은 생각이지만 null값을 두 번째 매개 변수 (상속 계층 구조 스프링이 주석을 사용하는 클래스를 스캔하는 클래스를 정의) null로 설정하면 구현에 따라 항상 돌아올 것입니다.
jonashackt


2

클래스 로딩은 "주문형"활동이므로 클래스 로더 API에는 "열거"메소드가 없습니다. 일반적으로 클래스 경로에 수천 개의 클래스가 있으며 그 중 일부만 필요합니다 (rt.jar 현재는 48MB입니다!).

당신 수 있다고해도 모든 클래스를 열거 시간과 메모리가 많이 소모됩니다.

간단한 접근 방식은 관련 클래스를 설정 파일 (xml 또는 원하는대로)에 나열하는 것입니다. 이를 자동으로 수행하려면 하나의 JAR 또는 하나의 클래스 디렉토리로 제한하십시오.


2

구글 리플렉션인터페이스도 찾고 싶다면 사용하십시오.

Spring ClassPathScanningCandidateComponentProvider이 인터페이스를 감지하지 않습니다.


1

Google Reflections 는 Spring보다 훨씬 빠릅니다. 이 차이점을 해결하는이 기능 요청을 찾았습니다. http://www.opensaga.org/jira/browse/OS-738

이것이 개발 중에 내 응용 프로그램의 시작 시간이 정말로 중요하기 때문에 Reflections를 사용하는 이유입니다. 리플렉션도 내 유스 케이스 (인터페이스의 모든 구현자를 찾습니다)에 사용하기가 매우 쉬운 것 같습니다.


1
JIRA 문제를 보면 안정성 문제로 인해 Reflections에서 멀어 졌다는 의견이 있습니다.
Wim Deblauwe

1

반사 에 대한 대안을 찾고 있다면 Panda Utilities-AnnotationsScanner 를 추천합니다. 합니다. 리플렉션 라이브러리 소스 코드를 기반으로하는 Guava-free (Guava는 ~ 3MB, Panda Utilities는 ~ 200kb) 스캐너입니다.

또한 미래 기반 검색 전용입니다. 포함 된 소스를 여러 번 스캔하거나 누군가가 현재 클래스 경로를 스캔 할 수있는 API를 제공하려면 AnnotationsScannerProcess모든 페치 된 캐시를 캐시 ClassFiles하므로 정말 빠릅니다.

간단한 AnnotationsScanner사용법 예 :

AnnotationsScanner scanner = AnnotationsScanner.createScanner()
        .includeSources(ExampleApplication.class)
        .build();

AnnotationsScannerProcess process = scanner.createWorker()
        .addDefaultProjectFilters("net.dzikoysk")
        .fetch();

Set<Class<?>> classes = process.createSelector()
        .selectTypesAnnotatedWith(AnnotationTest.class);

1

Spring에는 AnnotatedTypeScanner클래스 라는 것이 있습니다.
이 클래스는 내부적으로

ClassPathScanningCandidateComponentProvider

이 클래스에는 실제 클래스 경로 스캔 코드가 있습니다 리소스 있습니다. 런타임시 사용 가능한 클래스 메타 데이터를 사용하여이를 수행합니다.

이 클래스를 단순히 확장하거나 스캔에 동일한 클래스를 사용할 수 있습니다. 아래는 생성자 정의입니다.

/**
     * Creates a new {@link AnnotatedTypeScanner} for the given annotation types.
     * 
     * @param considerInterfaces whether to consider interfaces as well.
     * @param annotationTypes the annotations to scan for.
     */
    public AnnotatedTypeScanner(boolean considerInterfaces, Class<? extends Annotation>... annotationTypes) {

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