원래 스프링 빈을 직접 등록할때 방법은 두가지 방법이 있다.

  • 자바코드에서 @Bean 
  • xml 에서 <bean> 

이렇게 등록하면 수십, 수백개를 일일이 등록해야하는 귀찮음과 누락하는 문제점이 있다.

그래서 스프링 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 '컴포넌트 스캔' 이라는 기능을 제공한다.

 

 

 

 

컴포넌트 스캔하는 AutoAppConfig 클래스로 테스트해보자

기존의 설정정보인 AppConfig 대신해서 테스트용 설정정보 AutoAppConfig 클래스를 만들기

 

[기존의 AppConfig]

@Configuration
public class AppConfig {

	@Bean
	public MemberService memberService() {
		return new MemberServiceImpl(memberRepository());
	}
	
	@Bean
	public MemoryMemberRepository memberRepository() {
		return new MemoryMemberRepository();
	}
	
	@Bean
	public OrderService orderService() {
		return new OrderServiceImpl(memberRepository(), discountPolicy()); //discountPolicy()로 대체해주기
	}
	
	@Bean
	public DiscountPolicy discountPolicy() {
//		return new FixDiscountPolicy();  // 1만원이든 2만원이든 천원 할인적용
		return new RateDiscountPolicy(); // 1만원 천원할인, 2만원이면 2천원할인
	}
}

 

 

 

[테스트용 AutoAppConfig]

@Configuration //설정정보에 붙이면 싱글톤 유지
@ComponentScan(
	excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {

}

 

 

AutoAppConfig 에서는 @Bean 을 사용하지 않고 빈을 등록할 수 있다. 

그렇기 때문에 기존의 AppConfig 설정정보는 사용하지 않아서 제외시켜주자

 

@ComponentScan

 

  •   1. @Component 가 붙은 모든 클래스를 찾아(스캔해서) 자동으로 스프링 빈으로 등록해주는 어노테이션
  •   2. 기존의 AppConfig 설정정보는 사용하지 않아서 제외시켜준다.
    • @ComponentScan(
       excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
    • @Configuration이 붙은 Appconfig는 @Bean을 써서 스프링 빈을 수동등록 해놨기 때문에 @ComponentScan 에서 제외시킨다. 
    • 컴포넌트 스캔을 사용하면 @Configuration 이 붙은 설정 정보도 자동 등록해주기 때문에 excludeFilters 를 써서 제외시킨다.

 

근데 왜 컴포넌트 스캔은 @Configuration 도 스캔하는 것일까??

 

 

 @Configuration 이 컴포넌트 스캔의 대상이 된 이유

@Configuration 소스코드를 보면 @Component 가 붙어있기 때문! 

 

 

 

 

 

 

이제 AutoAppConfig 에서 @Bean 을 쓰지않고 어떻게 스프링 빈으로 등록을 할 수 있을까?

@Component 가 붙은 모든 클래스를 스캔해서 스프링 빈으로 등록해주기 때문에 각 클래스가 컴포넌트 스캔의 대상이 되도록 @Component를 붙여준다.  

 

** 어떤 클래스에 @Component 를 붙이나?

AppConfig 에서 @Bean이 붙은 메서드의 반환되는 객체의 타입이 스프링 빈으로 등록이 되니까 MemberServiceImpl, MemoryMemberRepository, OrderServiceImpl, RateDiscountPolicy 의 클래스에 @Component 를 붙이면 된다. 

 

 

MemberServiceImpl 에 @Component 붙이기

@Component
public class MemberServiceImpl implements MemberService{}

 

 

MemoryMemberRepository@Component 붙이기

 

@Component
public class MemoryMemberRepository implements MemberRepository{}

 

 

OrderServiceImpl에 @Component 붙이기

@Component
public class OrderServiceImpl implements OrderService{}

 

 

RateDiscountPolicy에 @Component 붙이기

@Component
public class RateDiscountPolicy implements DiscountPolicy{}

 

 

 

 

  • 스프링 빈의 기본 이름은 클래스명을 사용하는데 맨 앞글자를 소문자로 바꿔서 사용한다.
    • 빈 이름 MemberServiceImpl > memberServiceImpl 변경
    • 직접 빈이름을 지정 : @Component("memberServiceImpl2") 

 

 

 

그렇다면 이제 의존관계 주입은 어떻게 할것인가??

기존의 AppConfig의 의존관계 주입은 memberService 는 memberRepository 를 의존관계 주입을 했는데 

@Bean
public MemberService memberService() {
    return new MemberServiceImpl(memberRepository());
}

 

 

AutoAppConfig는 아무것도 없다 어떻게 의존관계 주입을 해야할까? @Autowired 를 사용하여 자동주입을 하면 된다.

@Configuration //설정정보에 붙이면 싱글톤 유지
@ComponentScan(
	excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {

}

 

 

 

 

 

@Autowired
의존관계를 자동으로 주입해주는 어노테이션

 

 

  1. @Component 붙은 클래스에서 의존관계 설정을 해주면 된다.  

이전에는 AppConfig에서는 @Bean 을 통해 직접 설정정보를 작성했고 의존 관계도 직접 연결했으나 

이제는 의존관계 주입을 @Component 붙은 클래스에서 생성자에 @Autowired를 붙여주면 된다. 

 

MemberServiceImpl @Autowired 추가

@Component
public class MemberServiceImpl implements MemberService{

	private final MemberRepository memberRepository;
	
	@Autowired 
	public MemberServiceImpl(MemberRepository memberRepository) {
		this.memberRepository = memberRepository;
	}

 

 

그 외 

OrderServiceImpl 클래스에 @Autowired 추가하기

@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;
	}
}

* @Autowired 는 생성자에서 여러 의존관계도 한번에 주입받을 수 있다.

 

 

 

 

@Autowired 동작 설명

 

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

 

@Autowired 
public MemberServiceImpl(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
}

 

어떻게?

  • MemberRepository 타입과 일치하는 스프링 빈을 찾아준다.
    • ac.getBean(MemberRepository.class) 와 같은 동작이라고 보면 된다.
  • MemberRepository 인터페이스라서 해당 인터페이스를 구현한 MemoryMemberRepository 빈을 찾아 MemberServiceImpl생성자에 넣어준다 
  • 이로써 MemberServiceImpl MemoryMemberRepository 사용할 수 있게된다. 

 

 

+ 만약 생성자의 파라미터에 주입하는 의존관계가 많다면??

@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}

 

  • OrderServiceImpl 생성자처럼 파라미터가 많아도 스프링 컨테이너에서 다 찾아서 자동으로 주입한다. 

 

 

 

 

테스트 했을때 로그를 보면 ComponentScan 이 잘 작동한것을 볼 수 있다.

public class AutoAppConfigTest {

	@Test
	void basicScan() {
		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
		
		MemberService memberService = ac.getBean(MemberService.class);
		Assertions.assertThat(memberService).isInstanceOf(MemberService.class);
	}
}

 

[결과]

 

 

 


 

@ComponentScan 부가 설명

 

@ComponentScan ( basePackages = "")
탐색 시작 위치 설정하기

 

@ComponentScan(
     basePackages = "hello.core.member",

 

  • 이렇게 하면 member 패키지만! 컴포넌트 스캔의 대상이 된다.  다른 패키지는 스캔이 안됨
  • 설정하는 이유?
    • 자바 코드뿐만 아니라 라이브러리까지 모두 스캔하기때문에 오래걸리기 때문에 지정하는것이 좋다.
  • 여러개 시작 위치를 지정가능
    • @ComponentScan(
           basePackages = { "hello.core.member", "hello.core.order" }
  • basePackageClasses : 클래스를 지정해주면 해당 패키지를 탐색 시작 위치가 된다
    • basePackageClasses = AutoAppConfig.class
    • AutoAppConfig 클래스의 패키지를 스캔한다.
  • 시작 위치를 지정을 하지 않는다면? 
    • @ComponentScan 이 붙은 설정정보 클래스의 패키지와 그 하위 패키지들을 모두 스캔한다 (디폴트)
    • 권장방법은 위치 설정(basePackages)을 해주기보다 설정정보 클래스의 위치를 프로젝트 최상단에 두자 

 

참고로 @ComponentScan 을 설정할 필요가 없다 왜냐

스프링 부트를 사용하면  메인 메소드가 있는 CoreApplication 클래스가 자동으로 만들어지는데 

@SpringBootApplication 안에 들어가보면 @ComponentScan 이 이미 있다.

@SpringBootApplication
public class CoreApplication {

	public static void main(String[] args) {
		SpringApplication.run(CoreApplication.class, args);
	}

}

 

 

 

그렇기 때문에 CoreApplication 의 패키지 hello.core부터 스캔을 해서 스프링 빈으로 등록이 되기때문에

굳이 @ComponentScan 을 할 필요는 없다 ^^  

 

 

 

 

반응형
LIST

+ Recent posts