Spring 핵심 원리 #9- 컴포넌트 스캔
Web/Java (Spring+JSP)

Spring 핵심 원리 #9- 컴포넌트 스캔

#컴포넌트 스캔과 의존관계 자동 주입

- 지금까지 스프링 빈을 등록할때 일일히 자바 코드(@Bean)이나 XML(<bean>)을 통해 등록하였다.

- 실무에 가서 스프링빈이 수십, 수백개가 될 수도 있는데 이는 설정 정보도 너무 커지고, 누락의 문제도 발생할 수 잇다.

 : So, 스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 Component Scan이라는 기능과 의존 관계도 자동으로 주입하는 @Autowired라는 기능도 제공한다.

 

Ex) 자동으로 빈을 등록하는 AutoAppConfig

@Configuration
@ComponentScan(
	//제외할 클래스를 명시 할 수 있다.
	excludeFilters= @Filter(type=FilterType.ANNOTATION, classes= Configuration.class))
    public class AutoAppConfig{
    }

 +) 기존의 @Configuration의 설정 정보를 이용하면 중복 실행 가능성이 있기 때문에 제외하기 위해 ExcludeFilter를 사용했다. 해당 연습에서만 그럴 예정..

 

- ComponentScan은 @Component 애노테이션이 붙은 클래스를 스캔해서 스프링 빈으로 등록한다.

@Component만: MemoryMemberRepository, RateDiscountPolicy (만들어둔 구현체 중 client에 주입할 클래스에 붙이면 됨), @Component, @Autowired 추가 : MemberServiceImpl, OrderServiceImpl (의존관계를 주입 받아야할 클래스에 붙임)

 

 

#작동 과정

- @ComponentScan은 @Component가 붙은 모든 클래스를 스프링 빈으로 등록한다.

- 기본 빈 이름을 사용해도 되고, @Component("빈 이름")으로 빈 이름을 지정해도 된다.

 

- 생성자에 @Autowired를 지정하면 스프링 컨테이너가 자동으로 스프링 빈을 찾아서 주입한다.

 : 기본적으로 타입이 같은 빈을 찾아서 주입한다. 생성자에 파라미터가 여러개여도 자동으로 모두 주입한다.

 

 

#탐색 위치와 기본 스캔 대상

- 모든 자바 클래스를 모두 컴포넌트 스캔하려면 시간이 오래 걸린다. 따라서 꼭 필요한 위치부터 탐색하도록 지정가능하다.

@ComponentScan(
	basePackage="hello.core"
    )

: 만약 따로 지정하지 않으면, @ComponentScan이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.

 

- 권장하는 방법은 패키지 위치를 따로 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 것이다.

: 이렇게 하면 해당 패키지를 포함한 하위 부분이 모두 자동으로 컴포넌트 스캔의 대상이 된다.

+) Spring Boot를 사용하면 스프링 부트의 대표적인 시작 정보인 @SpringBootApplication을 프로젝트 시작 루트 위치에 두는 것이 관례이며, 이 설정 안에 @ComponentScan이 포함되어있다.

 

 

#컴포넌트 스캔 기본 대상

: 컴포넌트 스캔은 @Component뿐만 아니라 다음의 내용도 추가로 대상에 포함한다.

- @Component : 컴포넌트 스캔에 사용

- @Controller : 스프링 MVC 컨트롤러에서 사용

- @Service : 스프링 비즈니스 로직에서 사용

- @Repository : 스프링 데이터 접근 계층에서 사용

- @Configuration : 스프링 설정 정보에서 사용, 스프링 빈이 싱글톤을 유지하도록 해준다.

 

 

#Filter

- includeFilters : 컴포넌트 스캔 대상을 추가로 지정한다.

- excludeFilters : 컴포넌트 스캔 에서 제외할 대상을 지정한다.

 

-> 예시를 통해 확인해보자

1) 컴포넌트 스캔 대상에 추가할 애노테이션 @MyIncludeComponent를 만든다.

2) 컴포넌트 스캔 대상에서 제외할 애노테이션 @MyExcludeComponent를 만든다.

3) BeanA에 @MyIncludeComponent를 적용한다

4) BeanB에 @MyExcludeComponent를 적용한다.

5) Test를 통해 확인한다.

public class ComponentFilterAppConfigTest{

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

        BeanA beanA = ac.getBean("beanA", BeanA.class);
        
        //BeanA는 등록되었고, BeanB는 따로 등록되지 않았다.
		assertThat(beanA).isNotNull();
        Assertions.assertThrows(
                NoSuchBeanDefinitionException.class,
				() -> ac.getBean("beanB", BeanB.class));
    }
    
    @Configuration
    @ComponentScan(
    	includeFilters=@Filter(type=FilterType.ANNOTATION,class=MyIncludeComponent.class),
        excludeFilters=@Filter(type=FilterType.ANNOTATION,class=MyExcludeComponent.class)
    )
    static class ComponentFilterAppConfig{}
	

}