매일 조금씩

[IoC 컨테이너와 빈] 4. @Component 와 컴포넌트 스캔 본문

Spring Framework/스프링 프레임워크 핵심 기술

[IoC 컨테이너와 빈] 4. @Component 와 컴포넌트 스캔

mezo 2024. 11. 4. 23:23
728x90
반응형

 

 

 

 

[전체 목차]

  1. IoC 컨테이너와 빈
  2. Resource / Validation
  3. 데이터 바인딩
  4. SpEL
  5. 스프링 AOP
  6. Null-Safety

 

 

 

1. 컴포넌트 스캔 주요 기능

@ComponentScan 은 spring 3.1부터 도입이 되었고,

가장 중요한 속성이 basePackage()이다.

basePackage()는 문자열을 받기 때문에 타입 세이프하지 않아서

타입 세이프 한 방법으로 설정할 수 있는 basePackageClasses()라는 속성이 또 있다.

이 속성은 베이스가 되는 패키지를 말하는데 이는 @ComponentScan가 붙여진 클래스가 포함된 패키지가 된다.

 

1) 스캔 위치 설정

일반적으로 Spring boot를 쓰면 애플리케이션 클래스에 @SpringBootApplication 어노테이션을 쓰는데

이 어노테이션을 타고 들어가면 SpringBootApplication 인터페이스가 아래 사진처럼 @ComponentScan을 붙이고 있다.

그래서 우리가 @ComponentScan를 직접 볼일은 드물다.

그게 아니더라도 @ComponentScan을 붙이고 있는 클래스가 스캔 시작 지점이 된다.

그래서 그 클래스를 포함하는 패키지 안의 것도 전부 스캔의 대상이 된다.

패키지 밖은 컴포넌트 스캔이 안된다.

이렇게 스캔의 범위를 지정하는 중요한 역할을 한다.

 

따라서, 스프링을 쓰다가 빈 주입이 잘안된다~~ 하면!

컴포넌트 스캔의 범위를 잘 따져봐야한다.

 

2) 필터: 어떤 어노테이션을 스캔 할지 또는 하지 않을지

컴포넌트 스캔의 또다른 특징중 하나가 필터다.

컴포넌트 스캔을 한다고 해서 모든 어노테이션을 빈으로 등록해주는건 아니다.

여러가지 필터 중, 위에선 ExcludeFiltersTypeExcludeFilterAutoConfigurationExcludeFilter 이 두개의 필터로 빈 생성을 거르고 있다.

 

TypeExcludeFilter는 주로 Spring boot에서 테스트 컨텍스트에서 테스트에 필요하지 않은 특정 타입의 빈을 제외할 때 사용되고, AutoConfigurationExcludeFilter은 Spring boot가 제공해주는 AutoConfiguration 관련 필터이다.

TypeExcludeFilter테스트나 특정 조건에서 불필요한 특정 타입의 빈을 제외하여, 로드되지 않도록 한다.

AutoConfigurationExcludeFilter자동 구성 클래스들이 스캔되지 않도록 필터링하여, 필요한 클래스만 빈으로 등록될 수 있도록 제한한다.

위 사진처럼 @Configuration이라는 어노테이션도 사실 @Component이기 때문에 빈으로 등록을 해주는데..

여기서 @ComponentScan@SpringBootApplication 에 붙어있고, Spring boot 에서 제공하는 AutoConfiguration 관련 빈을 등록해줄 필요가 없어서 저렇게 옵션으로 제외 해주고 있는 것으로 보인다.

 

2. @Component

스캔 위치와 범위, 그리고 필터링을 통해 정해진 스캔 범주 안의 클래스들 중, @Component 가 붙은 애들만 빈으로 등록이 된다.

기본적으로 @Component 어노테이션을 들고 있는 @Controller, @Service, @Repository, @Configuration같은 어노테이션이 붙은 클래스들은 모두 빈으로 등록이 된다.

 

3. 동작 원리

빈 등록은 BeanPostProcessor(의존성 주입 관련 인터페이스)가 아닌 BeanFactoryPostProcessor를 구현한ConfigurationClassPostProcessor와 연결이 되어있다.

BeanFactoryPostProcessorBeanPostProcessor와 비슷하지만 좀 다르다.

BeanFactoryPostProcessor는 다른 모든 빈들을 만들기 이전에 적용을 한다.

 

즉, 다른 빈들(우리가 @Bean이나 function 등을 써서 직접 등록하는 빈들)을 다 등록하기 전에 컴포넌트 스캔을 해서 BeanFactoryPostProcessor의 구현체들이 @Component(@Service, @Repository 등)인 클래스들을 빈으로 등록 해준다.

 

4. function을 사용한 빈 등록

ApplicationContext이 싱글톤 스코프인 빈들은 초기에 다 생성을 한다.

그래서 등록해야하는 빈이 많은 경우엔 초기 구동시간이 오래 걸릴 수가 있다.

그러나 이건 초기 구동 할때만 오래 걸리는거라 크게 문제가 되지 않는다고 본다.

 

다만, 초기 구동시간에 예민한 사람이라면 ㅎㅎ

아래처럼 function을 사용해서 빈을 등록할 수 있다.

public static void main(String[] args) {
        new SpringApplicationBuilder()
            .sources(Demospring51Application.class)
            .initializers((ApplicationContextInitializer<GenericApplicationContext>)
                applicationContext -> {
                applicationContext.registerBean(MyBean.class);
            })
            .run(args);
}

이렇게 function으로 빈을 등록하는 방법은 Reflection이나 CG Library, Proxy 같은 성능에 영향을 주는 기법을 사용하지 않기 때문에,

애플리케이션 구동타임이 빨라지는 성능 상의 이점이 조금이나마 있다.

그래서 빈이 많아서 초기 구동시간이 오래걸리는게 거슬리면 이방법을 쓰면 된다.

그러나, 이 방법이 componentScan을 대신하는 건 아니다. 난사하지 마라!!!

@Bean을 써서 직접 빈을 등록할때 이 방법을 쓰는건 괜찮을 것도 같다!

 

>> 기존 빈 생성엔 Reflection, CG Libray(CGLIB), Proxy를 사용하나?

그렇다. 일반적으로 Spring에서는 @Component, @Service, @Repository, @Configuration 등의 어노테이션이 붙은 클래스들을 스캔하고 빈으로 등록하기 위해 Reflection을 사용해 클래스 정보를 읽어오고, 의존성을 주입하기 위한 Proxy 객체를 생성하는 경우가 많다. Proxy 객체는 @Lazy와 같은 지연 초기화나 @Transactional 등의 AOP 관련 어노테이션이 붙은 객체를 말한다. CGLIB은 Spring AOP에서 트랜잭션 기능은 Proxy를 통해 추가되는데 이때, 인터페이스가 구현되어있지 않으면 사용된다.

Proxy 대상 객체에 인터페이스가 있을 땐 JDK 동적 Proxy를 사용하고, 없을 땐 CGLIB Proxy를 사용하여 필요한 기능을 적용한다.

 

>> 어노테이션을 구현한 클래스들은 왜 class도 아닌 interface도 아닌, @interface로 되어 있을까?

@interface어노테이션을 정의하기 위한 Java 문법이다.

Spring은 @interface로 정의된 어노테이션을 통해 메타데이터를 읽고, 특정 로직이나 설정을 적용하는 데 활용한다.

 

 

 

728x90
반응형