당니의 개발자 스토리
컴포넌트 스캔과 의존관계 자동 주입 시작하기 본문
6. 컴포넌트 스캔

이번 시간부터는 컴포넌트 스캔에 대해서 알아볼 건데요. 컴포넌트 스캔이랑 의존관계 자동 주입이라는 게 연결이 되어 있는 부분이 있어서 같이 쭉 설명을 드리겠습니다.
이번 시간은 컴포넌트 스캔과 의존관계 자동 주입에 대해서 시작을 해보겠습니다.

지금까지 스프링 빈을 등록할 때는 자바 코드의 @Bean이나 XML의 태그해서 <bean> 해가지고 설정정보에 직접 스프링 빈으로 등록하는 정보를 나열을 했었죠.
예제에서는 한 4개 밖에 안됐으니까 괜찮은데, 이렇게 등록해야 할 bean이 실무에서는 수백개가 된단 말이에요. 그러면 일일이 등록하기가 굉장히 귀찮아져요. 그리고 설정 정보가 엄청 커지고, 개발자 친구들이 또 누락한단 말이에요. 무엇보다도 개발자는 반복을 싫어해요. 왜냐면 클래스 만들기도 귀찮은데, new 해가지고 클래스 만드는데 또 빈으로 등록해줘야 되고 귀찮단 말이에요.
그래서 스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 '컴포넌트 스캔'이라는 기능을 제공해줍니다.
그리고 의존관계 주입도 자동으로 해주는 @Autowired 라는 의존관계 자동 주입 기능도 제공합니다.
굉장히 이것들을 사용하면 편리해지고 최근에 스프링 부트를 쓰면서는 더더욱 이 기능들을 많이 사용하게 되었습니다.
자 코드로 컴포넌트 스캔과 의존관계 자동 주입에 대해서 바로 알아보겠습니다.

먼저 기존 AppConfig.java는 과거 코드와 테스트를 유지하기 위해 남겨두고, 새로운 AutoAppConfig.java를 만들어볼게요.

설정 정보니까 @Configuration 이라고 적어 주고요.
자 컴포넌트 스캔이라는 건 스프링 빈을 쫙 긁어 가지고 자동으로 스프링 빈으로 끌어 올려야 되거든요.
그래서 @ComponentScan 이라는 애노테이션도 써야합니다.

@ComponentScan 이라고 하면, @Componen 애노테이션이 붙은 클래스를 찾아가지고 다 자동으로 스프링 빈으로 등록을 해줘요.
그런데 한 가지를 exclude 할 게요.

이렇게 하면, 내가 이제 쫘악 컴포넌트 스캔으로 다 뒤져가지고 다 스프링 빈으로 자동등록 하는데, 그 중에 뺄 거를 지정해주는 거에요.

뭘 뺼거냐면, @Configuration이 붙은 애들을 뺄 거에요.
'어? component 라는 거를 찾는다면서요! 왜 이건 빼나요?' AppConfig 때문에 그렇습니다.

이게 자동으로 등록되면 안되겠죠? AppConfig는 빈을 수동으로 등록하는 건데 이러면 충돌이 나겠죠. 우리가 확인해보고 싶은 건 스프링 빈 자동등록과 의존관계 자동 주입입니다.
이 @Configuration 이라는 애노테이션에 들어가면,

@Component 라는 게 붙어있습니다. 그럼 컴포넌트 스캔으로, @Component를 찾아서 스프링 빈으로 등록할텐데, AppConfig도 @Component니까 스프링 빈으로 등록을 해버리겠죠. 그걸 막기 위해서,

필터로 걸러주는 겁니다.

그래서 컴포넌트 스캔을 사용하려면 먼저 @ComponentScan을 설정 정보에 붙여주면 돼요.
그리고 기존의 AppConfig와는 다르게 @Bean으로 등록한 클래스가 하나도 없죠.

텅텅 비었죠.

참고로 컴포넌트 스캔을 사용하면 @Configuration이 붙은 설정 정보도 자동으로 스프링 빈에 등록이 돼요. 그래서 AppConfig나 우리가 전에 만들었던 TestConfig도 자동으로 스프링 빈에 등록되면서, 거기 안에 들어가있는 빈들도 다 등록이 되는 거에요. 이거는 수동 등록이랑 똑같은 거죠.
그래서 excludeFilters를 이용해서 설정 정보는 컴포넌트 스캔 대상에서 제외했습니다.
그러니까 '컴포넌트 스캔을 하긴 하는데, @Configuration 어노테이션이 붙은 것들은 뺄 거야' 라고 해서, FilterType을 Annotation으로 준 거죠. 보통 실무에서는 설정 정보를 컴포넌트 스캔 대상에서 제외하지는 않지만, 여기서는 기존 예제 코드를 최대한 남기고 유지하기 위해서 이 방법을 선택한거죠.

컴포넌트 스캔은 이름 그대로 @Component 애노테이션을 붙은 클래스를 스캔해서 스프링 빈으로 자동 등록해줍니다.
자 이제 @Component를 붙여야 되겠죠?
참고로 @Configuration이 컴포넌트 스캔의 대상이 된 이유도 @Configuration 소스 코드를 열어보면, @Component 애노테이션이 붙어있기 때문에 그랬습니다.
이제 @Component를 붙여봅시다.

FixDiscountPolicy는 붙여주지 말고, RateDiscountPolicy에다가 붙여줍니다.

그리고 MemberServiceImpl도 스프링 빈으로 등록해야하니까,

붙여줍니다. 여기까지 했는데 한가지를 더 해줘야 됩니다. 내가 MemberServiceImpl을 직접 스프링 빈으로 등록하는 게 아니에요. MemberServiceImpl이 스프링 빈으로 자동으로 등록되는 거예요.
그러면, '어랏? 의존관계 주입은 어떻게 하지?'
기존에 AppConfig를 봐보면,

'memberService는 memberRepository를 의존관계로 주입할 거야.' 라는 걸 명시적으로 볼 수 있었잖아요. 그런데 AutoAppConfig를 보세요.

아무것도 없단 말이에요.

그냥 애가 스프링 빈으로 떡 등록이 돼버리는 거에요.

그럼 이 memberRepository를 의존관계 주입으로 해줄 수 있는 방법이 없죠.
그래서 자동 의존관계 주입이 필요합니다.

의존관계를 자동으로 주입해주는 @AutoWired 라는 기능을 생성자에 붙여주면,

스프링이 여기에다가 MemberRepository 타입에 맞는 애를 스프링 컨테이너에서 찾아와서, 의존관계 주입을 Auto, 자동으로 wired, 연결해서 주입해 줍니다.
그래서 컴포넌트 스캔을 쓰면 이 @Autowired를 쓰게 돼요.
왜냐면 컴포넌트 스캔을 쓰면, 내 빈이 자동으로 등록이 되는데 의존관계를 설정할 수 있는 방법이 없잖아요. 왜냐면 AppConfig처럼 수동으로 등록할 수 있는 장소가 없는 거예요.
그래서 @Autowired를 쓰게 되면 스프링이 MemberServiceImpl도 스프링 빈으로 등록해주고, 그러면서 스프링 컨테이너에 있는 스프링 빈 중에 MemberRepository 타입에 맞는 MemoryMemberRepository를 여기에다가 주입해줍니다.

@Autowired가 마치 이렇게 동작한다고 보면 됩니다. 자동으로 ac.getBean(MemberRepository.class)가 들어간다고 보면 됩니다.
정리를 해보면,

이전에 AppConfig를 사용할 때는 @Bean으로 직접 설정 정보를 작성했고, 의존관계도 직접 명시했었죠. 이제는 이런 설정 정보 자체가 아예 없어지는 거예요. 일일이 등록하는 게 귀찮기 때문이죠.
그렇기 때문에 의존관계 주입도 이 클래스 안에서 어떻게든 해결해야 됩니다.
@Autowired는 의존관계를 자동으로 주입해줍니다. 자세한 룰은 조금 뒤에서 설명하겠습니다.
그리고 나머지 OrderServiceImpl도 똑같이 해줍니다.

@Autowired를 해주면, 스프링이 OrderServiceImpl을 생성할 때, 자동으로 ApplicationContext에서 memberRepository와 discountPolicy를 주입해줄 겁니다.

그래서 @Autowired를 사용하면 생성자에서 여러 의존관계도 한번에 주입받을 수 있습니다.
이제 테스트 코드를 한번 짜보겠습니다.

패키지를 scan이라고 할 게요.
그리고 AutoAppConfigTest 라는 클래스를 만들겠습니다.

얘도 스프링 컨테이너를 생성해주는데, AutoAppConfig를 전달해주는 데에서 차이가 있습니다.
이렇게 해놓고 조회해보면 되겠죠.

memberService가 잘 조회되는지 보고, 그 다음에

실행해보면, 잘 됩니다.

AnnotationConfigApplicationContext 를 사용하는 것은 기존과 동일해요. 그런데 설정 정보로 AutoAppConfig 클래스를 넘겨줬죠. 실행해보면 기존과 같이 잘 동작하는 것을 확인할 수 있고요.

로그를 잘 보면, 컴포넌트 스캔이 잘 작동하는 걸 확인할 수 있어요.

ClassPathBeanDefinitionScanner라는 게 뜨면서, candidate component인

이 클래스들이 후보 컴포넌트가 식별됐다고 하면서,

얘네가 싱글톤 빈으로 생성이 됐습니다. 라고 나옵니다.
그리고 @Autowired에 대한 정보도 나옵니다.

생성자에서 뭐가 주입이 됐는지 알려줍니다.
이제 그림으로 설명드리겠습니다. 컴포넌트 스캔과 자동 의존관계 주입이 어떻게 동작하는지 그림으로 알아봅시다.


@ComponentScan이 딱 되어있으면, 스프링 컨테이너가 @Component를 찾기 위해서 클래스들을 다 뒤집니다. 클래스를 다 뒤져가지고 거기서 @Component에 붙은 애들을 스프링 컨테이너에 스프링 빈으로 자동으로 다 등록합니다. 당연히 싱글톤으로 등록이 됩니다.
그런데 여기서 빈 이름을 주의하셔야 돼요. 스프링 빈의 기본 이름은 클래스명을 사용하되, 맨 앞글자만 소문자를 사용합니다. 예를 들어서, MemberServiceImpl 클래스는 memberServiceImpl이 됩니다.
참고로 이전에 우리가 수동으로 빈을 등록했을 때는 호출되는 메서드명을 사용했었죠.
여기서도 빈 이름을 내가 임의로 지정할 수도 있어요.
만약 스프링 빈의 이름을 직접 지정하고 싶으면, @Component("memberService2") 이런식으로 이름을 부여하면 됩니다.

이렇게 잘 바뀌는 걸 볼 수 있습니다. 웬만하면 디폴트로 쓰시고, 특별한 경우에만 이름을 부여하시면 됩니다.
그리고 @AutoWired는 어떻게 된 거냐면,

MemberServiceImpl 생성자에다가 @Autowired를 적었죠. 그러면 생성자니까 MemberServiceImpl을 생성하면서 스프링 컨테이너에 있는 MemberRepository 타입인 것들을 찾아봐요. MemberRepository 타입으로 조회합니다.
memoryMemberRepository는 MemberRepository의 자식 타입이니까, memoryMemberRepository를 꺼내서 주입해줍니다.
그런데 '어? 같은 타입이 여러 개 있으면요?' 그럼 충돌이 납니다. 그거는 뒤에 강의에서 설명을 해드릴 거예요.
아무튼 생성자에 @Autowired를 지정하면, 스프링 컨테이너가 자동으로 해당 타입의 스프링 빈을 찾아서 주입해줍니다.
이때 기본 조회 전략은 타입이 같은 빈을 찾아서 주입을 해요. 마치 getBean(MemberRepository.class)과 동일하다고 이해하면 됩니다. 그런데 좀 더 복잡한 메커니즘들이 있어요.
타입이 안 맞으면 어떻게 할 건지, 이름을 어떻게 부여할지 등등이 있는데, 더 자세한 거는 뒤에 @Autowired 의존관계 자동 주입에서 설명을 드릴게요.

그리고 OrderServiceImpl 같은 걸 보면, @Autowired 해서MemberRepository와 DiscountPolicy가 필요하죠. 타입으로 다 찾아가지고 착착착 주입해줍니다.

그래서 생성자의 파라미터가 많아도 다 찾아서 자동으로 주입을 해줍니다.
의존관계 주입을 보면 생성자 주입, 수정자 주입 이런 것들을 들어보셨을건데요. 그건 뒤에서 설명드릴게요.
그러면 한번 정리해 볼게요.
AutoAppConfig을 보면, @ComponentScan이 있으면 자동으로 우리의 클래스 path를 다 뒤져가지고 @Component가 붙은 것들을 다 스프링 빈에 등록을 해줍니다. 그러면 그때부터 찾아서 쓸 수 있겠죠.
그런데 스프링 컨테이너에 자동으로 등록하니까 무슨 문제가 생기냐면, 의존관계를 주입할 수 있는 방법이 없는 거예요.
이전에는 직접 수동으로 설정할 때는 내가 직접 설정 정보에다가 'MemberServiceImpl은 memberRepository를 의존관계로 주입해!' 이런걸 다 적어 줬는데, 컴포넌트 스캔은 설정 정보 자체를 안 쓰기 때문에 의존관계 자동 주입을 사용해서 주입을 하면 됩니다. 의존관계를 자동으로 주입해주는 애가 @Autowired 인 거죠. 찾을 때는 타입으로 찾아서 등록해줍니다.
다음 시간에는 컴포넌트 스캔 자체에 대해서 좀 더 자세히 설명을 해드리겠습니다.
'스프링 > 스프링 핵심 원리 - 기본편' 카테고리의 다른 글
| 필터 (0) | 2024.01.24 |
|---|---|
| 탐색 위치와 기본 스캔 대상 (0) | 2024.01.24 |
| @Configuration과 바이트코드 조작의 마법 (0) | 2024.01.23 |
| @Configuration과 싱글톤 (0) | 2024.01.23 |
| 싱글톤 방식의 주의점 (0) | 2024.01.23 |