Study/Java

annotation 을 가진 class 검색에 reflection util 대신 Spring TypeFilter 사용으로 대체하기

Bluesky_ 2023. 7. 31. 01:04
반응형

Reflection 소개

reflection 은 java의 classpath metadata를 scan 하고 캐싱하는 라이브러리이다.

https://github.com/ronmamo/reflections

다음과 같은 것들을 scan 할 수 있다.

  • type의 subtype
  • annotation으로 주석이 달린 type
  • annotatoin, parameters, return type이 있는 method
  • classpath 경로에서 찾을 수 있는 resource
  • ...

사용 방법은 간단하다.

dependency를 추가하고

# Maven
<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.10.2</version>
</dependency>

# Gradle
implementation 'org.reflections:reflections:0.10.2'

다음과 같이 사용할 수 있다.

Reflections reflections = new Reflections("io.github.luversof");

Set<Class<?>> subTypes =
  reflections.get(SubTypes.of(SomeType.class).asClass());

Set<Class<?>> annotated = 
  reflections.get(SubTypes.of(TypesAnnotated.with(SomeAnnotation.class)).asClass());

Spring Boot 3.0으로 변경하면서 java EE가 jakarta EE로 변경되었는데 reflection의 경우 더 이상 개발하지 않는 라이브러리인 관계로 해당 변경사항에 대한 지원이 없는 상태이다.

reflection은 jsr305 라이브러리 (javax.annotation.**)에 대한 의존성이 있다.

(2021년 10월 25일 0.10.2가 마지막 release)

이로 인해 기존 프로젝트의 버전 변경 시 reflection util을 사용하게 되면 불필요한 참조가 생기게 된다.

이로 인해 reflection 을 제거하고 대체를 해야 할 필요가 생겼다.

Spring TypeFilter로 대상 annotation 을 가진 class scan 하기

이럴 때 대안으로 사용할 수 있는 게 Spring Framework에서 제공하는 ClassPathScanningCandidateComponentProvider 이다.

위의 reflection을 사용한 경우의 예제는 ClassPathScanningCandidateComponentProvider 를 사용하여 다음과 같이 사용할 수 있다.

ClassPathScanningCandidateComponentProvider provider =
  new ClassPathScanningCandidateComponentProvider(false);
provider.addIncludeFilter(new AnnotationTypeFilter(SomeAnnotation.class));

Set<BeanDefinition> beanDefs = provider
  .findCandidateComponents("io.github.luversof");

Set<Class<?>> targetClassSet = new HashSet<>();
beanDefinitionSet.forEach(beanDefinition -> {
    try {
        targetClassSet.add(Class.forName(beanDefinition.getBeanClassName()));
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
});

위 경우 SomeAnnotation이란 annotation 을 지정한 type을 찾는 AnnotationTypeFilter 를 filter로 등록하여 찾았다.

AnnotationTypeFilter 뿐만 아니라 Spring이 구현해 사용하고 있는 여러 TypeFilter 중 필요한 기능의 TypeFilter를 찾아 사용하면 된다.

또한 custom TypeFilter 를 직접 구현하여 사용하는 것도 가능하다.

@FunctionalInterface
public interface TypeFilter {

    /**
     * Determine whether this filter matches for the class described by
     * the given metadata.
     * @param metadataReader the metadata reader for the target class
     * @param metadataReaderFactory a factory for obtaining metadata readers
     * for other classes (such as superclasses and interfaces)
     * @return whether this filter matches
     * @throws IOException in case of I/O failure when reading metadata
     */
    boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
            throws IOException;

}

@ComponentScan에서 TypeFilter의 사용

위 예제의 경우 같이 특정 annotation을 사용한 class를 찾기 위해 TypeFilter를 사용할 수도 있지만 Spring은 이를 Component Scan에서 사용할 수 있도록 제공하고 있다.

https://docs.spring.io/spring-framework/reference/core/beans/classpath-scanning.html#beans-scanning-filters

@ComponentScan annotation으로 scan 범위를 지정하는 경우를 예로 들면 다음과 같다.

@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    // ...
}

추가하는 FilterTypeCUSTOM 으로 지정하고 직접 구현한 TypeFilter 를 지정하면 된다.

참고 문서

https://www.baeldung.com/java-scan-annotations-runtime

반응형