annotation 을 가진 class 검색에 reflection util 대신 Spring TypeFilter 사용으로 대체하기
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에서 사용할 수 있도록 제공하고 있다.
@ComponentScan
annotation으로 scan 범위를 지정하는 경우를 예로 들면 다음과 같다.
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
// ...
}
추가하는 FilterType
을 CUSTOM
으로 지정하고 직접 구현한 TypeFilter
를 지정하면 된다.