ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring - 컴포넌트 스캔(Component Scan)
    Spring/Spring 2022. 1. 23. 21:47
    728x90
    반응형

     

    컴포넌트 스캔(Component Scan)

     

    • 스프링 빈을 등록할 때  @Bean 어노테이션을 이용하여 설정 정보에 직접 등록할 스프링 빈을 나열했다.
    • 등록해야 할 스프링 빈이 많을 경우에는 설정 정보도 커지고 누락하는 문제가 발생한다.
    • 이러한 문제를 해결하기 위해 스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록해주는 컴포넌트 스캔이라는 기능을 제공한다.
    • 의존 관계도 자동으로 주입하는 @Autowired라는 기능도 제공한다.

     

     컴포넌트 스캔을 하게 되면 AppConfig에서 해주었던 빈 등록을 하지 않아도 된다. 대신 @Component 어노테이션이 붙은 클래스를 스캔하여 스프링 빈으로 등록하게 된다.

     

    컴포넌트 스캔은 해당 패키지부터 하위 패키지까지 스캔해준다.

     

    @Configuration
    @ComponentScan
    public class AutoAppConfig {
    
    }

     

     

     이제 스프링 컨테이너에 빈 등록할 클래스(AppConfig에서 수동 등록했던 빈)에 @Conponent 어노테이션을 붙여준다.

     

    @Component
    public class MemoryMemberRepository implements MemberRepository {}
    
    @Component
    public class RateDiscountPolicy implements DiscountPolicy {}
    
    @Component
    public class MemberServiceImpl implements MemberService {
    
    	private final MemberRepository memberRepository;
        
    	@Autowired
    	public MemberServiceImpl(MemberRepository memberRepository) {
    		this.memberRepository = memberRepository;
    	}
    }
    
    @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;
    	}
    }

     

     AppConfig에서 수동으로 빈 등록을 할 때에는 의존 관계도 직접 명시했었다. @Conponent에서는 의존 관계는 @Autowired를 이용해 의존 관계 주입을 해준다.

     

     

    여기서 알아야 할 점은 수동으로 빈 등록과 의존 관계를 설정했을 때와는 달리
    컴포넌트 스캔을 이용하게 되면 구현체를 변경할 시 @Component를 A -> B로 변경해야
    하는 점으로 '수정'이 일어나는 트레이드 오프가 발생하게 된다.
    이 경우는 OCP를 위반하였지만  컴포넌트 스캔의 한계로 편리성을 위해 사용하는
    트레이드 오프로 이해하면 된다.

     

     

    컴포넌트 스캔의 관례

     

    • 컴포넌트 스캔은 해당 위치부터 하위 패키지를 모두 스캔해준다.
    • 컴포넌트 스캔을 시작하는 패키지 위치를 지정해줄 수 있지만 일반적으로
      메인 설정 정보 클래스의 위치를 프로젝트 최상단에 둔다.
    • 메인 설정 정보 클래스는 프로젝트를 대표하는 정보이기 때문에 프로젝트 최상단에 둔다.
    • 참고로 스프링 부트의 대표 시작 정보인 @SpringBootApplication
      프로젝트 시작 루트 위치에 두는 것이 관례이다.

     

     

    컴포넌트 스캔의 기본 대상

     

     해당 어노테이션들의 소스 코드를 보면 @Component를 포함하고 있는 것을 알 수 있다.

    • @Component : 컴포넌트 스캔에서 사용
    • @Controller : 스프링 MVC 컨트롤러에 사용,
                        스프링 MVC 컨트롤러로 인식
    • @Service : 스프링 비즈니스 로직에서 사용
    • @Repository : 스프링 데이터 접근 계층에서 사용
                        스프링 데이터 접근 계층으로 인식, 데이터 계층의 예외를 스프링 예외로 변환해준다.
    • @Configuration : 스프링 설정 정보에서 사용
                             스프링 설정 정보로 인식, 스프링 빈이 싱글톤으로 유지하도록 추가 처리

     

     

    컴포넌트 스캔의 필터

     

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

     

     필터의 예시를 들기 위해 사용자 정의 어노테이션을 만들어보자

    package hello.core.scan.filter;
    import java.lang.annotation.*;
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MyComponent {
    
    }
    
    @MyIncludeComponent
    public class BeanA {
    
    }

     

     이제 설정 정보 클래스에서 필터를 적요해 보자

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

     

     includeFilters를 이용하면 스캔 범위에 없는 해당 클래스(BeanA)를 컴포넌트 스캔을 할 수 있다. 반대로 excludeFilters를 이용하면 스캔 범위에 있는 해당 클래스(BeanA)를 컴포넌트 스캔을 하지 않도록 제외할 수 있다.

     

     

    위 필터는 거의 사용할 일이 없으니 사용법만 알아두도록 하자.

     

     

    빈의 중복 등록과 충돌

     

    • 같은 빈 이름으로 @Component를 이용하여 2번 등록
      자동 빈 등록으로  같은 이름의 빈을 2번 등록할 시
      - ConflictingBeanDefinitionException 예외 발생

    • 같은 빈 이름으로 @Component와 @Bean을 이용하여 2번 등록
      자동 1번, 수동 1번으로 같은 이름의 빈을 2번 등록할 시
      - 수동으로 등록 한 빈이 우선권을 가진다(수동 빈이 자동 빈을 오버라이딩 해버린다.)
      - 하지만 이와 같은 경우는 잡기 어려운 버그가 만들어지므로 스프링 부트에서는 오류를 발생시키도록 변경됨
    // 자동 등록
    @Component
    public class MemoryMemberRepository implements MemberRepository {}
    
    
    @Configuration
    @ComponentScan
    public class AutoAppConfig {
    	// 수동 등록
    	@Bean(name = "memoryMemberRepository")
    	public MemberRepository memberRepository() {
     		return new MemoryMemberRepository();
    	}
    }

     

     

     

     

     

     

     

     

    728x90
    반응형

    댓글

Designed by Tistory.