Section 07 - 컴포넌트 스캔

청주는사과아님·2024년 12월 23일
0
post-thumbnail

Github Repository

@ComponentScan 자체는 원래 알고 있던 내용이지만 basePackageClasses, Filter 등 몰랐던 내용이 있어 간단히 정리하고자 한다.

일단 @ComponentScan 자체는 아래처럼 구성되어 있다.

package org.springframework.context.annotation;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {

    Class<?>[] basePackageClasses() default {};

    Filter[] includeFilters() default {};

    Filter[] excludeFilters() default {};

    /* ... 생략 ... */

}

basePackageClassesbasePackage 랑 거의 동일한데, 어느 클래스가 속한 package 를 basePackage 로 지정해주는 속성이기 때문이다.

또한 includeFilters, excludeFilters@ComponentScan 이 클래스를 포함, 제외할 filter 를 설정하는 속성이다.

참고로 @Filter 어노테이션은 @ComponentScan 속에 Nested interface 로 정의되어 있다.

public @interface ComponentScan {

    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    @interface Filter {

        FilterType type() default FilterType.ANNOTATION;

        @AliasFor("classes")
        Class<?>[] value() default {};

        @AliasFor("value")
        Class<?>[] classes() default {};

        String[] pattern() default {};
    }
}

(여담으로 Java SE 에 따르면 중첩 인터페이스는 클래스, 인터페이스 body 에 존재하는 인터페이스이다. 꼭 클래스 내부에 있어야 '중첩 인터페이스' 라 부르는 건 아니다.) [1]

FilterTypeEnum 상수로 정의되어 아래처럼 분류된다.

FilterType
1. ANNOTATION :
특정 어노테이션을 기반으로 필터를 구성

  1. ASSIGNABLE_TYPE :
    클래스, 인터페이스 등의 타입을 기반으로 필터를 구성
  1. ASPECTJ, REGEX :
    AspectJ, 정규표현식 등 패턴을 기반으로 필터를 구성
  1. CUSTOM :
    사용자가 직접 만든 FilterType 을 통해 필터를 구성
public enum FilterType {
	ANNOTATION,
	ASSIGNABLE_TYPE,
	ASPECTJ, REGEX,
	CUSTOM
}

이를 이용해 아래처럼 "특정 어노테이션이 붙은 개체를 Bean 생성에서 제외" 하는 등 좀 더 유연한 동작이 가능하다.

(물론 정말로 사용할 일이 있는지는 몰?루)


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyExclusion {

}

@Configuration
@ComponentScan(
        includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = AbstractSupClass.class),
        excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = {MyExclusion.class})
)
class Config {

}

조금 더 테스트하다 알게 된 사실인데, FilterType.ASPECTJ 를 이용하려면 실제 AspectJ 라이브러리가 있어야 작동하는 걸 발견했다.

처음엔 클래스 weaving time 차이로 인한 문제일 줄 알았는데 spring-core 라이브러리를 디버깅하다 정확한 원인을 발견했다.

(Spring 기본은 RTW, AspectJ 는 CTW 또는 LTW 로 기억한다.)

@ComponentScanexcludeFilters 혹은 includeFilters 를 사용하면 TypeFilterUtilscreateTypeFiltersFor 메서드가 호출된다.

이 때 FilterType 에 따라 알맞게 filter 를 생성하는데, FilterType.ASPECTJ 이면 새로운AspectJTypeFilter 를 생성하려고 한다.

그런데 AspectJTypeFilter 를 보면 실제 org.assertj 에 사용되는 World, TypePattern 이 필요함을 볼 수 있고, 이 때문에 실제 AspectJ 라이브러리가 필요하다.

즉, 아래처럼 만들어야 FilterType.ASPECTJ 를 사용할 수 있다.

// build.gradle

dependencies {
    /* ... */

    implementation 'org.springframework.boot:spring-boot-starter-aop'
    
    // starter-aop 에는 애초에 aspectj:aspectjweaver 를 depends on 한다.
    // aspectj 라이브러리도 같이 딸려온다는 소리.
}

@Configuration
@ComponentScan(
        excludeFilters = @Filter(type = FilterType.ASPECTJ, pattern = "*..SomeClass")
)
public class TestConfig {

}

하지만 풀리지 않는 의문점이 존재하는데, "왜 FilterType.ASPECTJ 를 안 사용했을 때는 잘 작동하는가?" 이다.
결국 문제가 되는 이유는 TypeFilterUtils 에서 AspectJTypeFilter 를 사용하려 하고, AspectJTypeFilter 에는 AspectJ 라이브러리를 필요로 하기 때문이었다.

따라서 애초에 build 시 AspectJTypeFilter 를 필요로 할 것이고, AspectJTypeFilter 를 컴파일 하는 과정에서 AspectJ 라이브러리가 없어 에러가 발생해야 한다.

하지만 확인해보니 정말 이상하게 FilterType.ASPECTJ 를 사용했을 때만 build 실패가 발생하고 사용하지 않았을땐 실패하지 않는다.

혹시 SpringDependency ManagementRuntime 에 행해지는지 확인하려 했지만 정확한 공식 문서를 잘 찾지 못했다.

일단 내가 무언가 놓쳤을 지도 모르므로 한번 Spring-framework 깃헙에 해당 문제를 issue 로 물어본 상태이다.
뭔가 새롭게 알게 된 내용이 있다면 추가하겠다.

++ AspectJTypeFilter 에러 update

직접 Spring-framework 에 물어보니 이는 의도된 행동이라고 한다.

나는 AspectJ 가 유명하니까 이 패턴대로 정의해 사용하세요! 라는 의도로 만들어진 줄 알았는데, AspectJ 기능을 선택해 작동 하기 위해 만들어졌다고 한다.

또한 FilterType.ASPECTJ 를 사용했을 때만 테스트가 실패해 생겼던 궁금증은 Spring 과 관련된 내용이 아니라 JavaClass loading 과 관련된 내용이었다.

나는 이전에 Java Compile 시점에 바이트 코드 변환 뿐만 아니라, 필요한 클래스들의 loading, linking 까지 이루어진다고 생각했었다.

하지만 알아보니 Class loading, Linking 은 런타임 시점에 이루진다 한다.

JavaC 처럼 생각해버린 것이다.

어느 부분에서 내가 잘못 생각했는지는 파악했지만, 이를 정확하게 정리하려면 Java Class loading 에 대한 공부가 필요할 것 같다.
더 깊게 들어가면 JVM 자체까지 들어가야 될 수도 있을 것 같다.

지금 당장 공부할 계획은 없으므로 나중을 위해 관련 자료만 첨부하겠다.


Reference

  • Chapter 9. Interfaces - Java Specification
    • [1] : A nested interface is any interface whose declaration occurs within the body of another class or interface declaration. A nested interface may be a member interface (§8.5, §9.5) or a local interface (§14.3).

profile
나 같은게... 취준?!

0개의 댓글