
김영한 강사님의 스프링 핵심 원리의 강의 내용과 자료를 이용했음을 밝힙니다.
지금까지 우리가 배운 모든 것이 스프링 빈에 대해 배웠다고 해도 과언이 아니다.
자 이 스프링 빈, 만들기 정말 간단하다.
그냥 @Bean 애노테이션만 사용하면 금방 만들어지니까....?
과연 실무에서도 그럴까?
스프링 빈이 수십 수백개가 될 수도 있다. 거기다가 @Bean 애노테이션을 사용해서 등록하는 거 간단할까?
설정 정보가 커지면 누락할 가능성도 생기고 심지어 수백개의 스프링 빈을 일일히 등록하는 것은 매우 반복적이고 귀찮은 일이다.

자 내가 앞에서 이 말은 한 이유는 당연히 눈치챘겠지만 말해보자면,
스프링은 스프링 빈을 자동으로 등록시켜주는 기능을 제공한다!
(파면 팔수록 기능이 많단말이지.... 좋아 좋아)
바로 컴포넌트 스캔(component scan)이라는 기능이다.
그러면 그동안 프로그램의 제어 흐름을 담당했던 AppConfig 대신
컴포넌트 스캔을 이용한 AutoAppConfig 클래스를 작성해보겠다.
@Configuration
@ComponentScan(
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes =Configuration.class))
public class AutoAppConfig {
}
이게 끝이다. ㅎㅎ
"어라? 아니 memberService, orderService 이런 얘들은 어디다 두고?"
그런 구성요소를 다 적는게 불편해서 @ComponentScan을 사용하는 건데 그런 구성정보를 다시 다 적어야 하는 것은 말이 안된다.
우선 코드 좀 설명을 하자면
컴포넌트 스캔을 사용하기 위해서는 @Configuration 밑에 @ComponentScan 을 작성해줘야 한다.
그리고 여기서 Filter를 사용했는데 간단히 정리하면
- includeFilters : 컴포넌트 스캔 대상을 추가로 지정한다.
- excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정한다
그리고 FilterType으로는
- ANNOTATION: 기본값, 애노테이션을 인식해서 동작한다.
- ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작한다
- ASPECTJ: AspectJ 패턴 사용
- REGEX: 정규 표현식
- CUSTOM: TypeFilter 이라는 인터페이스를 구현해서 처리
이렇게 있는데 읽고만 넘어가자.
excludeFilters를 통해서 @Configuration이 붙은 설정정보들은 다 제외했는데
그 이유는 일단 @Configuration 안에 @Component 애노테이션이 포함되어 있기에 스캔 대상이고
그러면 @Configuration를 사용한 AppConfig나 test들이 다 스캔대상에 포함되서 제외했다.
(일반적이지는 않지만 코드 보존을 위한 방법이다.)
자 그러면 위에서의 의문을 이제 풀어보자.
ComponentScan은 말 그래도 파일들을 스캔하면서 빈 등록을 해주는 것이다.
그래서 넣을 class 객체들 위에 @Component만 넣어주면 되는 것이다.
밑의 코드는 한 파일이 아니라 각기 다 다른 class 들이다.
@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의 코드와 비교하면서 하나씩 비교해보자.
//AppConfig
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
//AutoAppConfig
@Component
public class MemoryMemberRepository implements MemberRepository {}
컴포넌트 스캔은 @Component 애노테이션이 붙은 클래스를 스캔을 하고 스프링 빈으로 등록한다.
그래서 @Component 애노테이션을 붙인다.
스프링 빈의 기본 이름은 클래스명에서 앞글자만 소문자로 변경하고 사용된다.

물론 @Component("memberRepository2") 이런 식으로 직접 지정도 가능하다.
//AppConfig
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
//AutoAppConfig
@Component
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
그러면 MemberService와 같이 안에서 memberRepository를 호출해야 하는 상황 즉 의존관계를 주입해줘야 하는 상황은 어떻게 해야 할까?
그때 사용할 수 있는 애노테이션이 @Autowired이다.
컴포넌트 안에 주입해줘야 하는 객체를 생성해놓고 위와 같이 생성자에 @Autowired를 지정하면 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입해준다.

어떤 걸 주입해냐 하면은 일단은 타입이 같은 빈을 찾아서 주입하는데 타입이 같은 빈이 여러 개 일수도 있고 좀 복잡하다고 한다.
다음에 알려준다는데 흠..
모든 패캐지를 다 뒤지면서 컴포넌트를 찾으면 매우 비효율적임이 확실하고 시간도 많이 걸릴 것이다.
그래서 스캔의 시작 패캐지를 지정해줄 수 있다.
// 패캐지 한 개
@ComponentScan(
basePakages = "hello.core",
)
// 패캐지 두 개 이상
@ComponentScan(
basePakages = {"hello.core", "hello.service"}
)
이렇게 basePakages를 통해 탐색 시작 지점을 지정을 해주면 그 패캐지의 하위 패캐지까지 모두 탐색한다.
"만약 탐색 시작 지점을 지정하지 않는다면?"
@ComponentScan이 붙은 설정 정보 클래스의 패캐지가 시작 위치가 된다.
그래서 권장되는 방법이 있다.
프로젝트가
이런 식으로 구성되어 있으면 맨 위에 있는 com.hogwart에 @ComponentScan이 붙은 AppConfig를 넣어서 하위를 모두 scan 대상으로 만드는 것이다.
또한 프로젝트 메인 설정 정보는 프로젝트를 대표하기에 프로젝트의 시작 루트 위치에 넣는 것이 좋다.
추가로
- @Component : 컴포넌트 스캔에서 사용
- @Controller : 스프링 MVC 컨트롤러에서 사용
- @Service : 스프링 비즈니스 로직에서 사용
- @Repository : 스프링 데이터 접근 계층에서 사용
- @Configuration : 스프링 설정 정보에서 사용
컴포넌트 스캔이 이 애노테이션들도 스캔 대상에 포함시킨다고 한다.
그냥 알고만 넘어가자.
@ComponentScan에서 같은 이름으로 빈 등록을 하면
=> ConflictingBeanDefinitionException 예외가 발생한다.
그러면 하나는 @ComponentScan을 통해 자동 빈 등록을 하고 하나는 @Bean을 이용해서 수동 등록을 한다면?
=> 수동 빈 등록이 덮어쓴다 즉 이긴다(override)
하지만 이것은 다른 개발자가 이해하기에 명확하지 않깅 스프링 부트에 가면 부트가 오류를 일으키면서 튕긴다.(뭐 굳이 사용하려면 할 수는 있음..)
사실 이번 챕터에서는 더 깊게 알아볼 수 있는데 그렇게까지 깊게 알아볼 필요는 없어서 그냥 넘어간 느낌이 없잖아 있다. 약간 찜찜?
하지만 점점 더 간편해지는 것들을 보면서 실무는 스프링을 어떻게 사용할지 궁금해진다.