본문 바로가기

Spring

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

컴포넌트 스캔

 이전에는 AppConfig와 같이 DI 컨테이너를 만들고 그안에 @Bean 어노테이션을 사용해서 메소드(객체 반환)를 등록하였다. 하지만 만일 등록할 빈이 너무 많다면 일일히 하나하나 빈을 등록하는 일은 여간 어려운 일이 아닐 것이다. 그렇기에 좀더 빈 등록을 편하게 하기위해서 컴포넌트 스캔을 사용한다. 

 - 예시

  -기존방식

기존 MemberServiceImpl.java

package hello.core.member;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

public class MemberServiceImpl implements MemberService{
    //인터페이스에만 의존(DIP 원칙 준수)
    private final MemberRepository memberRepository;
    
    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Override
    public void join(Member member) {
        memberRepository.save(member);
    }

    @Override
    public Member findMember(Long memberId) {
        return memberRepository.findById(memberId);
    }
}

기존 DI 컨테이너 AppConfig.java

package hello.core;

@Configuration 
public class AppConfig {

    @Bean
    public MemberService memberService(){
        System.out.println("call AppConfig.memberService");
        return new MemberServiceImpl(memberRepository());
    }
    @Bean
    public MemberRepository memberRepository() {
        System.out.println("call AppConfig.memberRepository");
        return new MemoryMemberRepository();
    }
    @Bean
    public OrderService orderService(){
        System.out.println("call AppConfig.orderService");
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }
    @Bean
    public DiscountPolicy discountPolicy(){
//        return new FixDiscountPolicy();
        return new RateDiscountPolicy();
    }

}

 기존에는 위에서와 같이 예시로든 MemberServiceImpl 객체를 AppConfig의 생성자 주입 메소드(memberService( );)를 사용해서 @Bean을 이용해 스프링 빈으로 등록하였다.

 

  1. 객체로 등록하고자 하는 클래스 정의 
  2. DI 컨테이너를 이용하여 생성자 주입
  3. 객체를 반환하는 메소드에 @Bean 어노테이션을 이용해서 객체들을 스프링 빈으로 등록

 

  -컴포넌트 스캔 방식(@Component, @ComponentScan)

 기존에는 @Bean을 이용해서 일일히 수동으로 빈을 등록해주어야 했다. 빈은 결과적으로 객체이고 객체는 클래스를 new 한 것으로 생각할 수 있다.(맞나?)

 뭐 그렇기에 이제부터는 @Component 어노테이션과 @ComponentScan 어노테이션을 이용해서 등록하고자 하는 클래스 객체를 편리하게 등록하겠다.

 

컴포넌트 스캔 방식을 이용한 MemberServiceImpl.java

package hello.core.member;

@Component  //컴포넌트 스캔 대상 (빈 등록 대상)
public class MemberServiceImpl implements MemberService{
    //인터페이스에만 의존(DIP 원칙 준수)
    private final MemberRepository memberRepository;

    @Autowired  // 스프링에 등록된 MemberRepository 빈을 가지고 자동으로 의존관계 주입
    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Override
    public void join(Member member) {
        memberRepository.save(member);
    }

    @Override
    public Member findMember(Long memberId) {
        return memberRepository.findById(memberId);
    }

}

컴포넌트 스캔 방식을 이용한 DI 컨테이너 AutoAppConfig.java

package hello.core;

@Configuration
@ComponentScan(
        basePackages = "hello.core.member",
        excludeFilters= @ComponentScan.Filter(type = FilterType.ANNOTATION, 
        	classes = Configuration.class)
)
public class AutoAppConfig {

}

  AutoAppConfig AppConfig를 비교해보자. AppConfig에 비해서 AutoAppConfig가 훨씬 간소해진 것을 확인 할 수 있다. 심지어 클래스 내부에는 어떤 것도 써있지 않다. 대신 AppConfig와 같이 @Configuration이 있고 AppConfig와 달리 @ComponentScan이 있는 것을 볼 수 있다.

 

 AutoAppConfig는 설정정보 클래스이고 설정정보 클래스는 @Configuration이 있는 것이 당연하다. 이건 간단하다.

그리고 @ComponentScan은 @Component가 붙은 클래스를 확인해서 해당 클래스 객체를 스프링 빈으로 등록하는 역할을 한다.

 

 위에 MemberServiceImpl을 보자. @Component가 붙은 것을 확인 할 수 있다. 따라서 MemberServiceImpl은 AutoAppConfig의 @ComponentScan에 의해서 자동으로 빈으로 등록된 것이다.

 

  -의존 관계 자동 주입 @Autowired

 그리고 MemberServiceImpl을 보면 생성자에 @Autowired라는게 붙어있는 것을 볼 수 있다. 기존 AppConfig 에서는

사용자가 직접 자바 코드를 이용해서 생성자 주입을 해주었다.

 

기존 의존관계 주입

@Bean
public MemberService memberService(){
    System.out.println("call AppConfig.memberService");
    return new MemberServiceImpl(memberRepository());//인자로 MemberRepository 객체 넣어줌(수동 DI)
}

근데 컴포넌트 스캔을 이용하면 기존 방식으로는 의존 관계 주입을 해줄 수 없다. 

 

MemberServiceImpl의 생성자

@Autowired  // 스프링에 등록된 MemberRepository 빈을 가지고 자동으로 의존관계 주입
public MemberServiceImpl(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
}

AutoAppConfig.java

package hello.core;

@Configuration
@ComponentScan(
        basePackages = "hello.core.member",
        excludeFilters= @ComponentScan.Filter(type = FilterType.ANNOTATION, 
        	classes = Configuration.class)
)
public class AutoAppConfig {

}

 여기만 보면 MemberServiceImpl 클래스는 @Component가 붙어서 자동으로 빈으로 등록이 되는데 AutoAppConfig에는 생성자 주입을 담당하는 코드가 없다. (사실 걍 아무것도 없다.) . 그렇기에 우리는 MemberServiceImpl의 생성자의 인자로 뭔가를 넣어줄 방법이 없다. 그래서 @Autowired가 필요한 것이다.

 @Autowired는 컴포넌트 스캔된 클래스의 생성자의 인자에 이미 스프링 빈으로 등록된 객체들중 타입이 알맞은 녀석을 찾아서 넣어준다.

 즉, MemberServiceImpl의 인자로는 기존에 스프링 빈에 등록된 MemberRepository 객체를 찾아서 자동으로 주입해준다는 것이다.

 

@Component로 컴포넌트 스캔
@Autowired를 이용한 의존관계 자동 주입 (스프링이 인자로 적절한 빈을 찾아 넣어줌)

 컴포넌트 스캔 탐색 위치

AutoAppConfig.java

package hello.core;

@Configuration
@ComponentScan(
        basePackages = "hello.core.member",
        excludeFilters= @ComponentScan.Filter(type = FilterType.ANNOTATION, 
        	classes = Configuration.class)
)
public class AutoAppConfig {

}

 사실 모든 자바 클래스를 전부 컴포넌트 스캔하려면 너무 시간이 오래 걸리기 때문에 탐색할 위치를 지정할 수 있다.

basePackages = "hello.core.member"는 member 패키지와 member의 하위 패키지를 컴포넌트 스캔한다는 것이다.  따로  탐색위치를 지정하지 않는다면 (@ComponentScan 인자에 따로 안 적어주는 경우) 디폴트는 @Configuration이 적힌 설정정보 클래스의 패키지 위치가 시작위치가 된다.

  그래서 추천하는 방식은 패키지 위치를 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 것이다.

예를들어 hello.core에 설정정보 클래스를 두는 것이다.

구성 정보 클래스는 최상단인 hello.core에 만듦

컴포넌트 스캔 기본 대상

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

'Spring' 카테고리의 다른 글

Spring- 의존관계 자동 주입  (0) 2023.01.29
Spring- 컴포넌트 스캔시 필터  (0) 2023.01.26
Spring- @configuration과 싱글톤  (0) 2023.01.24
Spring- 싱글톤  (0) 2023.01.24
Spring- 스프링 빈 메타 정보 설정(BeanDefinition)  (0) 2023.01.19