Spring/Spring core basic

[Spring core basic] 28 - 컴포넌트 스캔과 의존관계 자동 주입

Kloong 2022. 5. 2. 01:30

참고

더보기

Spring core basic 시리즈는 김영한 님의 "스프링 핵심 원리 - 기본편" 강의를 정리한 글입니다. 글에 첨부된 사진은 해당 강의의 강의 자료에서 캡쳐한 것입니다. 제 Github에만 올려뒀다가, 정보 공유와 강의 홍보(?)를 위해 블로그에도 업로드합니다. 마크다운을 잘 쓰지 못해서 가독성이 조금 떨어지는 점 양해 바랍니다.

 

 

스프링 핵심 원리 - 기본편 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...

www.inflearn.com

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

지금까지는 스프링 빈을 등록할 때는 자바 코드의 @Bean 이나 XML의 <bean> 등을 통해서 설정 정보에 직접 등록할 스프링 빈을 나열했다.

예제에서는 몇개가 안되었지만, 이렇게 등록해야 할 스프링 빈이 수십, 수백개가 되면 일일이 등록하기도 귀찮고, 설정 정보도 커지고, 누락하는 문제도 발생한다.

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

또 의존관계도 자동으로 주입하는 @Autowired 라는 기능도 제공한다.

컴포넌트 스캔 @ComponentScan

AutoAppConfig.java

package com.kloong.corebasic1;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
}

컴포넌트 스캔을 사용혀려면 먼저 @ComponentScan annoation을 설정 정보 클래스에 붙여주면 된다. 기존의 AppConfig와는 다르게 @Bean 으로 등록하는 클래스에 대한 정보가 하나도 없는 것을 확인할 수 있다.

*참고: 컴포넌트 스캔을 사용하면 @Configuration annotation이 붙은 설정 정보도 자동으로 등록되기 때문에, AppConfig, TestConfig 등 앞서 만들어두었던 설정 정보도 함께 등록되고, 실행되어 버린다.

그렇게 되면 AppConfig의 @Bean 이 붙어있는 메소드가 실행되면서 컴포넌트 스캔에 의해 빈이 등록되는 것이 아니라 자바 코드에 의해 수동으로 빈이 등록되어버린다. 따라서 컴포넌트 스캔에 대한 내용을 제대로 학습할 수 없기 때문에 해당 설정 정보를 컴포넌트 스캔 대상에서 제외했다.

기존 예제 코드를 유지하기 위해서 파일을 지우지 않았기 때문에 이렇게 하는거지, 보통 설정 정보를 컴포넌트 스캔 대상에서 제외하지는 않는다. 앞에서 AppConfig가 빈으로 등록되는 것도 확인했다.

컴포넌트 스캔은 이름 그대로 @Component annotation이 붙은 클래스를 스캔해서 스프링 빈으로 등록한다. 스프링 빈으로 등록하고 싶은 클래스에 @Component 를 붙여주자.

*참고: @Configuration 이 컴포넌트 스캔의 대상이 된 이유도 @Configuration 소스코드를 열어보면 @Component annotation이 포함되어있기 때문이다.

빈으로 등록할 클래스에 @Component 붙이기

@Component
public class MemoryMemberRepository implements MemberRepository { /* 생략 */ }

@Component
public class RateDiscountPolicy implements DiscountPolicy{ /* 생략 */ }

@Component
public class MemberServiceImpl implements MemberService{ /* 생략 */ }

@Component
public class OrderServiceImpl implements OrderService{ /* 생략 */ }

의존관계 자동 주입 @Autowired

그런데 코드를 아무리 살펴봐도 어떤 클래스가 빈으로 등록될 지에 대한 정보만 존재할 뿐이지 의존관계 주입에 대한 설정 내용이 아무데도 없다는 것을 알 수 있다. 기존의 AppConfig에는 의존관계 주입이 명시가 되어있었는데, AutoAppConfig에는 아무런 내용이 없기 때문이다.

MemberServiceImpl이나 OrderServiceImpl의 경우 외부에서의 의존관계 주입이 필요하다.

따라서 컴포넌트 스캔 방식을 사용하기 위해서는 의존관계 주입에 대한 설정을 해주기 위해 @Autowired annotation을 사용해야 한다.

MemberServiceImpl.java

//package, import 생략

@Component
public class MemberServiceImpl implements MemberService{

    //DIP 만족. 인터페이스에만 의존한다. 어떤 구현체가 들어올지 모른다.
    private final MemberRepository memberRepository;

    //의존관계 주입을 위해 @Autowired 사용
    @Autowired
    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    //생략
}

@Autowired 를 생성자에 붙여주면, 스프링이 생성자의 파라미터의 타입에 맞는 빈을 찾아서 의존관계를 자동으로 주입해준다.

이는 마치 ac.getBean(MemberRepository.class) 를 해서 찾은 빈을 주입해주는 것과 비슷하다 (디테일하게 들어가면 다른 부분이 있다고 한다).

마찬가지로 OrderServiceImpl에도 생성자에 @Autowired 를 붙여주면 된다.

OrderServiceImpl.java

//package, import 생략

@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 를 사용하면 생성자에서 여러 의존관계도 한번에 주입받을 수 있다.

잘 동작하는지 테스트 해 보자.

AutoAppConfigTest.java

//package, import 생략

public class AutoAppConfigTest {

    @Test
    void basicScan() {
        ApplicationContext ac =
        new AnnotationConfigApplicationContext(AutoAppConfig.class);

        MemberService memberService = ac.getBean(MemberService.class);
        Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }
}

AnnotationConfigApplicationContext를 사용하고, 설정 정보 클래스 AuotoAppConfig를 넘겨주는 것은 동일하다. 테스트도 잘 된다.

로그를 살펴보면

Identified candidate component class: file [/Users/kloong/Spring/spring_core_basic/corebasic1/build/classes/java/main/com/kloong/corebasic1/discount/RateDiscountPolicy.class]

이런식으로 컴포넌트의 후보가 될 클래스를 찾았다는 로그도 있고,

Creating shared instance of singleton bean 'autoAppConfig'

싱글톤 빈을 만들었다는 로그도 있고,

Autowiring by type from bean name 'memberServiceImpl' via constructor to bean named 'memoryMemberRepository'

Autowiring(의존 관계 자동 주입)에 대한 로그도 있는 것을 확인할 수 있다.

정리

1. 컴포넌트 스캔 @ComponentScan

  • @ComponentScan 에 의해 @Component 가 붙은 모든 클래스를 스프링 빈으로 등록한다.
  • 이 때 스프링 빈의 default 이름은 클래스 명을 사용하되, 맨 앞글자를 소문자로 바꿔서 사용한다.
    • MemberServiceImpl -> memberServiceImple
    • 스프링 빈 이름을 직접 지정하고 싶으면 @Component("원하는이름") 이런 식으로 이름을 지정하면 된다.

2. 의존관계 자동 주입 @Autowired

  • 생성자에 @Autowired 를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다.
  • 이때 기본 조회 전략은 타입이 같은 빈을 찾아서 주입한다.
    • ac.getBean(MemberRepository.class) 와 동일하다고 이해하면 된다.
    • 그런데 만약 타입이 같은 빈이 여러개 존재한다면? -> 이런 디테일한 부분은 뒤에서 설명한다.

@Autowired 가 붙은 생성자에 파라미터가 많아도 잘 찾아서 자동으로 주입해준다.