[TIL] _ 260312 _ 컴포넌트 스캔을 제대로 파헤쳐보다

호두·2026년 3월 13일

✔️ Java / Spring

목록 보기
6/25

✅ 컴포넌트 스캔

설정정보에 직접 스프링 빈을 등록하는 방법 VS 스프링 컴포넌트 스캔으로 스프링 빈 자동 등록 + @Autowired 기능 (의존관계 자동 주입)


ComponentScan 은 @SpringBootApplication 에 들어있다. 그래서 직접 사용하지 않고 앞에서 애노테이션만으로 스프링빈 자동 등록을 사용할 수 있었다.

수동 컴포넌트 스캔

package hello.core;


import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

import static org.springframework.context.annotation.ComponentScan.*;

@Configuration // 설정파일 이다.
@ComponentScan( // @SpringBootApplication 에 들어있는 애노테이션이라 직접 사용하지 않았었음.
       excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = Configuration.class))

public class AutoAppConfig {


}

➡️ excludeFilter 는 내가 만들어 놓은 설정파일들에 @Configuration 이 있어서 직접 만든 설정파일을 사용하지 않고 스프링이 해주는 컴포넌트 스캔을 사용하기 위해서 직접 만든 거는 제외시킨 것.

여기에 @Component 를 각각 스프링 빈 클래스에 적어두면 스프링이 알아서 생성해 스프링 컨테이너에 넣어둔다.

그리고 @Autowired 로 의존관계를 주입한다.


컴포넌트 애노테이션

컴포넌트를 가진 애노테이션들을 가지면 스프링이 부가 기능을 수행한다.

  • @Controller : 스프링 MVC 컨트롤러로 인식
  • @Repository : 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해준다.

데이터 계층의 예외와 스프링 예외가 뭘까?

데이터를 저장하고 있는 리포지토리는 DB 라는 외부 기능과 상호작용한다. 이때 DB 는 Oracle 이나 MySQL 등의 여러 종류가 있는데 각각의 DB들이 던지는 예외 방식이 다 다르다. 다 다른 예외를 스프링이 예외를 추상화해서 신경쓰지 않고 예외를 다룰 수 있도록 변환해준다.
이렇게 스프링이 추상화를 해준 덕분에 나중에 DB를 바꿨을 때 예외가 변경되도 클라이언트는 신경쓰지 않아도 된다.

변하는 것(기술) 과 변하지 않는 것(비지니스 로직) 을 철저히 분리하려는 노력의 결과다.


  • @Service : 핵심 비지니스 로직의 위치만 알리는 역할만 한다. 서비스는 중간에 위치해서 외부 기능과 접촉하지 않는 완충지대에 위치하고 있어서 추가적인 기능을 가질 필요가 없고, 또 중요한 비지니스 로직을 다루기 때문에 어떤 기술에 종속적이면 안된다.

필터

includeFilter : 컴포넌트 스캔 대상을 추가로 지정
excludeFilter : 컴포넌트 스캔에서 제외할 대상을 지정

컴포넌트 스캔 대상으로 지정하는 애노테이션

package hello.core.scan.filter;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}

컴포넌트 스캔 대상에서 제외시키는 애노테이션

package hello.core.scan.filter;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {
}
package hello.core.scan.filter;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

import static org.assertj.core.api.Assertions.*;
import static org.springframework.context.annotation.ComponentScan.*;

public class ComponentFilterAppConfigTest {

    @Test
    void filterScan() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);

        BeanA beanA = ac.getBean("beanA", BeanA.class);
        assertThat(beanA).isNotNull();

        org.junit.jupiter.api.Assertions.assertThrows(NoSuchBeanDefinitionException.class,
                () -> ac.getBean("beanB", BeanB.class));
    }

    @Configuration
    @ComponentScan(includeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class))
    @ComponentScan(excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class))


    static class ComponentFilterAppConfig {
    }
}

컴포넌트 스캔 대상으로 설정한 BeanA 는 null 이 발생하지 않았고, 제외시킨 BeanB 는 NoSuchBeanDefinitionException 이 발생하는 걸 확인 할 수 있었다. 직접 애노테이션을 만드는 방법을 알게되서 유익했다.

⚠️ 직접 애노테이션을 만들 때 인터페이스에서 사용할 애노테이션들은 직접 치기보다 복사해오는게 좋다 (import 자동추가 안되서)

0개의 댓글