[스프링 핵심 원리] 06. 컴포넌트 스캔 - 컴포넌트 스캔과 의존관계 자동 주입 시작하기
(인프런) 김영한님의 스프링 핵심 원리-기본편을 공부하고 리뷰한 글입니다.
1. 컴포넌트 스캔과 의존관계 자동 주입 시작하기
지금까지는 자바 코드의 @Bean이나 XML의 <bean>을 통해서 설정 정보에 직접 스프링 빈을 등록했다.
예제에서는 등록해야할 스프링 빈이 몇 개가 안됐지만 규모가 커지면 반복과 누락의 문제가 있다... 그래서,
1) 스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공한다.
2) 의존관계도 자동으로 주입하는 @Autowired 라는 기능도 제공한다.
기존 AppConfig.java는 과거 코드, 테스트를 유지하기 위해 남겨두고 새로운 AutoAppConfig.java를 만들자.
1. AutoAppConfig.java - @ComponentScan
1) 컴포넌트 스캔을 사용하려면 @ComponentScan을 설정 정보에 붙여준다.
2) 기존의 AppConfig와는 다르게 @Bean으로 등록한 클래스가 하나도 없다!
package hello.core;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
// 설정 정보
@Configuration
// @Component가 붙은 클래스를 자동으로 스프링 빈으로 등록(@Configuration이 붙은 클래스는 제외)
@ComponentScan(
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
}
(참고) 컴포넌트 스캔을 사용하면 @Configuration이 붙은 설정 정보가 자동으로 스프링 빈으로 등록된다.
이전에 만들었던 AppConfig, TestConfig 등의 설정 정보를 스프링 빈으로 등록되면 안되기 때문에 excludeFilters 를 사용하여 컴포넌트 스캔 대상에서 제외했다. (보통 설정 정보를 컴포넌트 스캔 대상에서 제외하지는 않음)
Q. @Configuration이 붙은 클래스를 컴포넌트 대상에서 제외하면 AutoAppConfig도 제외되는 것이 아닌가?
AutoAppConfig는 @Configuration이 붙어있긴 하지만, 아래와 같이 직접 등록했기 때문에 컴포넌트 스캔과 무관하게 스프링 빈으로 등록된다.
new AnnotationConfigApplicationContext(AutoAppConfig.class)
[참고] https://www.inflearn.com/questions/462112
@ComponentScan에 대해서 질문드립니다. - 인프런 | 질문 & 답변
안녕하세요 다름이 아니라 강의 초반 부분에서 AutoAppConfig에 @ComponentScan에서 @Configuration부분을 대상에서 제외하셨는데 그러면 AutoAppConfig에 있는 @Configuration부분도 여기 excludeFilters에 의해 제외가
www.inflearn.com
2. @Component, @Autowired 추가하기
각 클래스가 컴포넌트 스캔의 대상으로서 자동으로 스프링 빈 등록되도록 @Component 애노테이션을 붙인다.
1) MemoryMemberRepository @Component 추가
@Component
public class MemoryMemberRepository implements MemberRepository{
...
}
MemoryMeberRepository는 자동으로 스프링 빈으로 등록된다.
2) RateDiscountPolicy @Component 추가
@Component
public class RateDiscountPolicy implements DiscountPolicy{
...
}
RateDiscountPolicy는 자동으로 스프링 빈으로 등록된다.
3) MemberServiceImpl @Component, @Autowired 추가
@Component
public class MemberServiceImpl implements MemberService{
// MemberServiceImpl 은 MemberRepository 인터페이스에만 의존
private final MemberRepository memberRepository;
// 의존관계 자동 주입(DI)
@Autowired // ac.getBean(MemberRepository.class)
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
...
}
MemberServiceImpl은 자동으로 스프링 빈으로 등록된다.
MemberServiceImpl의 생성자의 파라미터로 MemberRepository 타입의 빈이 자동 주입된다.
4) OrderServiceImpl @Component, @Autowired 추가
@Component
public class OrderServiceImpl implements OrderService{
// 인터페이스에만 의존하도록 변경(DIP 만족)
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
// 의존관계 자동 주입(DI)
@Autowired // ac.getBean(MemberRepository.class), ac.getBean(DiscountPolicy.class)
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
...
}
OrderImpl은 자동으로 스프링 빈으로 등록된다.
OrderServiceImpl의 생성자의 파라미터로 MemberRepository, DiscountPolicy 타입의 빈이 자동 주입된다.
3. AutoAppConfigTest.java
AnnotationConfigApplicationContext()의 설정정보를 AutoAppConfig 클래스로 넘겨준다. (나머지 부분은 동일)
1) 코드 작성
package hello.core.scan;
import hello.core.AutoAppConfig;
import hello.core.member.MemberService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.*;
public class AutoAppConfigTest {
@Test
void basicScan() {
// 설정 정보를 AutoAppConfig로 넘겨서 스프링 컨테이너 생성
// AutoConfig는 스프링 빈으로 직접 등록됨
ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
// MemberService 조회
MemberService memberService = ac.getBean(MemberService.class);
// memberService 인스턴스가 MemberService 타입인지 검증
assertThat(memberService).isInstanceOf(MemberService.class);
}
}
Q. new AnnotationConfigApplicationContext() 로 생성할 때 ApplicationContext 타입으로 선언한 이유는?
사용하는 코드가 ApplicationContext의 제약을 따르기 때문에 향후 변경시에 사용코드를 변경하지 않아도 된다. 구현체를 AnnotationConfigApplicationContext -> 다른 구현체 로 변경한다고 하더라도 실제 ApplicationContext 인스턴스를 사용하는 코드는 변경할 필요가 없다는 뜻이다.
클래스 외부에서 구현 객체를 생성해서 파라미터로 넘길 경우, 구현 객체가 다른 것으로 바뀌어도 다른 코드는 전혀 변경하지 않는다.
[참고] https://www.inflearn.com/questions/47449
private static Map<Long, Member> store = new HashMap<>(); 관련 질문입니다. - 인프런 | 질문 & 답변
안녕하세요. 존경하는 개발자님 질문 내용은 다음과 같습니다. private static Map<Long, Member> store = new HashMap<>(); 일반적으로 인스턴스를 생성할때에 다음과 같이 생성합니다. 상위객체 = new 하위객체(
www.inflearn.com
2) 결과
로그를 보면 컴포넌트 스캔이 잘 동작해 스프링 빈이 자동으로 등록된 것을 확인할 수 있다.
4. 컴포넌트 스캔과 자동 의존 관계 주입 동작 그림
1) @ComponentScan
@ComponentScan은 @Component가 붙은 모든 클래스를 자동으로 스프링 빈으로 등록한다.
- @Configuration이 붙은 클래스도 스프링 빈으로 자동 등록된다. (@Configuration 내부에 @Component 가 있음)
<스프링 빈 이름>
1) 빈 이름 기본 지정: 클래스 명(맨 앞글자만 소문자로 변경)
e.g. MemberServiceImpl 클래스 -> memberServiceImpl
2) 빈 이름 직접 지정: @Component(스프링 빈 이름)
e.g. @Component("memberService2") -> memberService2
2) @Autowired 의존관계 자동 주입
생성자에 @Autowired를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다.
- 기본 조회 전략은 타입이 같은 빈을 찾아서 주입하는 것이다.
- 생성자에 파라미터가 많아도 여러 의존관계를 한번에 주입받을 수 있다.