[Spring] 기본_컴포넌트 스캔

gayoung·2022년 3월 16일
0

스프링 완전 정복

목록 보기
21/33

1. 컴포넌트 스캔과 의존관계 자동 주입 시작하기

1-1. 기존 의존관계 주입 방법

  • 지금까지 스프링 빈을 등록할 때는 자바 코드의 @Bean이나 XML의 <bean> 등을 통해서 설정 정보에 직접 등록

[ AppConfig ]

public class AppConfig {

    @Bean  // @Bean을 통해 스프링컨테이너에 등록됨
    public MemberSerivce memberService() {
        return new MemberServiceImpl(memberRepository());  // 생성자 주입
    }
}
  • 이것의 문제점
    • 등록해야 하는 스프링 빈이 수백개라면 등록하기 힘들고 설정정보 커짐
  • 스프링은 설정정보 없어도 자동으로 스프링빈을 등록하는 컴포넌트 스캔이라는 기능 제공
  • @Autowired: 의존관계 자동 주입

1-2. 컴포넌트 스캔을 이용한 의존관계 주입

[ AutoAppConfig ]

@Configuration  // 들어가보면 @Component 붙어있음
@ComponentScan(  // @Component가 붙은 클래스를 찾아 자동으로 등록해주는데, 그 중에서 제외할 것
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
// @Configuration은 수동으로 등록해주는 것이므로 제외시켜야함
)
// 보통 설정 정보를 컴포넌트 스캔 대상에서 제외하지는 않지만, 기존 예제 코드를 최대한 남기고 유지하기 위해서 이 방법을 선택했다.
public class AutoAppConfig {
    }
}
  • 컴포넌트 스캔을 사용하려면 먼저 @ComponentScan 을 설정 정보에 붙여줘야함
  • 기존의 AppConfig와는 다르게 @Bean으로 등록한 클래스가 하나도 없음
  • 컴포넌트 스캔은 @Component 붙은 클래스를 스캔해서 스프링 빈으로 등록(@Configuration 안에 들어가보면 @Component 붙어있음)
  • @Autowired 를 이용해 여러 의존관계를 한번에 주입

[ MemoryMemberRepository ]

@Component
public class MemoryMemberRepository implements MemberRepository {}

[ RateDiscountPolicy ]

@Component
public class RateDiscountPolicy implements DiscountPolicy {}

[ OrderServiceImpl ]

@Component
public class OrderServiceImpl implements OrderService {
    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    @Autowired
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
}
  • Test 시 ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class); 사용

[ 자동 의존관계 주입 순서 ]

  1. AutoAppConfig 클래스에 @ComponentScan 작성
    • @ComponentScan 은 @Component 가 붙은 모든 클래스를 스프링 빈으로 등록
    • 빈 이름 : 첫글자 소문자(memberServiceImpl) MemberServiceImpl 클래 - 빈 이름 직접 지정: @Component("memberService2")이런식으로 직접 지정하면 됨
  2. @Autowired 의존관계 자동 주입
    • getBean(MemberRepository.class)와 동일

2. 탐색 위치와 기본 스캔 대상

2-1. 탐색할 패키지의 시작 위치 지정

@ComponentScan(
        basePackages = "hello.core",
        basePackageClasses = AutoAppConfig.class,  // 이 클래스에서도 찾음
)
  • basePackages: 시작점 찾기
    • default는 이 파일이 있는 위치. 현재 hello.core바로 밑에 있으므로 default = hello.core임. 만약, member파일에서 시작하고 싶다면, hello.core.member라고 작성하면 됨.
    • 패키지 위치를 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 것
    • 여러개 작성 가능
  • basePackageClasses: 지정한 클래스의 패키지를 탐색 시작 위치로 지정
    • 만약 지정하지 않으면 @ComponentScan이 붙은 설정 정보 클래스의 패키지가 시작 위치가 됨
  • 스프링 부트를 사용하면 스프링 부트의 대표 시작 정보인 @SpringBootApplication를 이
    프로젝트 시작 루트 위치에 두는 것이 관례임(@SpringBootApplication 안에 @ComponentScan 들어있음)

2-2. 컴포넌트 스캔 기본 대상

  • @Component
  • @Controlller: 스프링MVC 컨트롤러로 인식
  • @Service: 다른역할없이 비즈니스 계층 인식하는데 도움을 줌
  • @Repository: 스프링 데이터 접근 계층으로 인식, 데이터계층의 예외를 스프링 예외로 변환
  • @Configuration: 스프링 설정 정보로 인식, 스프링 빈이 싱글톤을 유지하도록 함

3. 필터

[ includeFilters ]

  • 컴포넌트 스캔 대상을 추가로 지정
@Target(ElementType.TYPE)  // TYPE이면 클래스레벨에 붙음
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}

[ excludeFilters ]

  • 컴포넌트 스캔에서 제외할 대상을 지정
@Target(ElementType.TYPE)  // TYPE이면 클래스레벨에 붙음
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {
}

[ BeanA ]

@MyIncludeComponent
public class BeanA {
}

[ BeanB ]

@MyExcludeComponent
public class BeanB {
}

[ excludeFilters ]

public class ComponentFilterAppConfigTest {

    @Test
    void filterScan() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);
        BeanA beanA = ac.getBean("beanA", BeanA.class);
        Assertions.assertThat(beanA).isNotNull();

        // beanB는 찾을 수 없음
        // why? Filter에서 excludeFilters에 해당하기 때문
        BeanB beanB = ac.getBean("beanB", BeanB.class);
        org.junit.jupiter.api.Assertions.assertThrows(
                NoSuchBeanDefinitionException.class,
                () -> ac.getBean("beanB", BeanB.class)
        );
    }

    @Configuration
    @ComponentScan(
            // FilterType.ANNOTATION이 기본값이므로, 안적어도 괜찮
            includeFilters = {
                    @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class)
            },
            excludeFilters = {
                    @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class),
                    // A도 제거하겠다면
                    // @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = BeanA.class)  
            }
    )
    static class ComponentFilterAppConfig {

    }
}

4. 중복등록과 충돌

  • 자동 빈 등록 vs 자동 빈 등록
    • 컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록되는데, 이름이 같은 경우 스프링은 오류 발생 시킴
  • 수동 빈 등록 vs 자동 빈 등록
    • 수동 빈이 자동 빈을 오버라이딩 해버려서, 수동 빈 등록이 우선권을 가짐

0개의 댓글