05. Spring Basic - 컴포넌트 스캔

MoonJaeGyeong·2023년 9월 8일

Spring

목록 보기
5/10

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


지금까지 스프링 빈과 의존관계를 모두 자바 코드의 @Bean 이나 XML 의 <bean> 등을 통해서 설정 정보에 직접 등록해주었다.

이제 스프링에서 제공하는 자동으로 빈을 등록해주는 @Component 기능을 사용해보자.

AppConfig.java

@Configuration
public class AppConfig {
    @Bean
    public MemberService memberService(){
        System.out.println("call AppConfig.memberService");

        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public MemoryMemberRepository memberRepository() {
        System.out.println("call AppConfig.memberRepository");
        return new MemoryMemberRepository();
    }

    @Bean
    public OrderService orderService(){
        System.out.println("AppConfig.orderService");
        return  new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    @Bean
    public DiscountPolicy discountPolicy(){
        //return new FixDiscountPolicy();
        return new RateDiscountPolicy();
    }
}

AutoAppConfig.java

@Configuration
@ComponentScan(
 	excludeFilters = @Filter(type = FilterType.ANNOTATION, classes =
   Configuration.class))public class AutoAppConfig {
}
  • 컴포넌트 스캔을 사용하려면 @ComponentScan 을 설정 정보에 붙여주면 된다.
  • 기존의 AppConfig.java 와는 다르게 @Bean 으로 등록한 클래스가 하나도 없다.



이제 각 클래스가 컴포넌트 스캔의 대상이 되도록 @Component 를 붙여주자.

MemoryMemberRepository @Component 추가

@Component
public class MemoryMemberRepository implements MemberRepository {}

RateDiscountPolicy @Component 추가

@Component
public class RateDiscountPolicy implements DiscountPolicy {}

MemberServiceImpl @Component, @Autowired 추가

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

OrderServiceImpl @Component, @Autowired 추가

@Component
public class OrderServiceImpl implements OrderService {
 	private final MemberRepository memberRepository;
 	private final DiscountPolicy discountPolicy;
 	@Autowired
	 public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicydiscountPolicy) {
 		this.memberRepository = memberRepository;
 		this.discountPolicy = discountPolicy;
 	}
}



2. 컴포넌트 스캔과 자동 의존관계 주입 작동 원리


  1. @ComponenetScan

컴포넌트 스캔

  • @ComponentScan@Component 가 붙은 모든 클래스를 스프링 빈으로 등록한다.
  • 스프링 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자만 소문자를 사용한다.
    - 만약 스프링 빈의 이름을 직접 지정하고 싶으면 @Component("Beanname") 이렇게 하면 된다.
  1. @Autowired 의존관계 자동 주입
    의존관계 자동 주입
  • 생성자에 @Autowired 를 지정하면 자동으로 해당 스프링 빈을 찾아 주입한다.
  • 기본 조회 전략은 타입이 같은 빈을 찾아서 주입한다. ( getBean(MemberRepository.class) 와 동일하다)



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


모든 자바 클래스를 다 컴포넌트 스캔 한다면 낭비가 심하다. 그래서 꼭 필요한 위치부터 탐색하도록 시작 위치를 지정할 수 있다.

@ComponentScan(
 basePackages = "hello.springcore",
}
  • basePackages : 탐색할 패키지의 시작 위치를 지정.
    - basePackages = { "hello.springcore", "hello.service" } 여러 시작 위치를 정할 수 있다.
  • basePackageClasses : 지정한 클래스의 패키지를 탐색 시작 위치로 지정.
  • 만약 지정하지 않으면 @ComponentScan 이 붙은 설정 정보 클래스의 패키지가 시작위치가 된다.

컴포넌트 스캔은 @Componenet 뿐만 아닌 아래 내용도 추가로 대상에 포함된다.

  • @Controller : 스프링 MVC 컨트롤러에서 사용
  • @Service : 스프링 비즈니스 로직에서 사용
  • @Repository : 스프링 데이터 접근 계층에서 사용
  • @Configuration : 스프링 설정 정보에서 사용



4. 필터


  • includeFilters : 컴포넌트 스캔 대상을 추가로 지정.
  • excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정.

예제로 확인해 보자.

컴포넌트 스캔 대상에 추가할 애노테이션

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

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

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

컴포넌트 스캔 대상에 추가할 클래스

@MyIncludeComponent
public class BeanA {
}

컴포넌트 스캔 대상에 제외할 클래스

@MyExcludeComponent
public class BeanB {
}

설정 정보와 테스트 코드

public class ComponentFilterAppConfigTest {
 @Test
 void filterScan() {
   ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);
   BeanA beanA = ac.getBean("beanA", BeanA.class);
   assertThat(beanA).isNotNull();
   Assertions.assertThrows( NoSuchBeanDefinitionException.class, 
   () -> ac.getBean("beanB", BeanB.class));
 }

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



5. 자동 빈 등록 vs 수동 빈 등록


빈 등록이 두 번 된다면 어떻게 될까?

@Component
public class MemoryyMeberRepository implements memberRepositroy {}
@Configuration
@ComponentScan(
 	excludeFilters = @Filter(type = FilterType.ANNOTATION, classes =
Configuration.class)
)

public class AutoAppConfig {
	 @Bean(name = "memoryMemberRepository")
 	public MemberRepository memberRepository() {
 		return new MemoryMemberRepository();
 	}
}

이 경우 수동 빈이 자동 빈을 오버라이딩 해버린다.

하지만 최근 스프링 부트에서는 이 경우 아래와 같은 오류를 낸다.
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

<출처 : 스프링 핵심 원리 - 기본편 by 김영한>

profile
내 맘대로 끄적이는 개발 블로그

0개의 댓글