이제까지는 설정 정보, 그러니까 @Configuration을 이용하여 스프링 빈을 등록해주었다. 하지만 이렇게 일일히 스프링 빈을 등록하는 것도 굉장히 귀찮은 일이다. 프로젝트의 규모가 커지면(언제나 세계 규모의 프로젝트를 진행한다고 상상을 하며 공부를 한다) 빈으로 등록해주어야 할 클래스들도 엄청나게 늘어날 것이다.
이런 귀찮은 일을 해소하기 위해 만들어진 것이, 컴포넌트 스캔이다.
컴포넌트 스캔을 이용하여 의존관계를 자동 주입하는 방법은 간단하다.
구성 파일로 사용하고자 하는 클래스에 @ComponenetScan을, 스프링 빈으로 등록하고자 하는 클래스에는 @Component를 붙이면 된다.
@ComponentScan
public class AppConfig {}
@Component
public class MemoryMemberRepository implements MemberRepository {
private static Map<Long, Member> store = new HashMap<>();
@Override
public void save(Member member) { ... }
@Override
public Member findById(Long memberId) { ... }
}
이렇게 하면, 자동으로 스프링 빈에 MemoryMemberRepository가 등록이 된다. 이름은 수동으로 등록할 때 와 동일한 규칙을 적용하여, 따로 지정을 하지 않을 경우 첫 문자만 소문자로 바꾸어 memoryMemberRepository로 스프링 빈에 등록된다.
즉, @Component가 붙은 클래스가 스캔의 대상이 되어 스프링 컨테이너에 등록이되는 것이다.
그리고 중요한 애노테이션이 하나 더 있는데, 바로 @AutoWired이다.
이렇게 OrderServiceImpl 같은 경우에는 다른 두 가지의 클래스 memberRepository와 discountPolicy에 의존적이다.
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
/* 생략 */
}
그렇기에 기존의 AppConfig에서 직접적으로 의존관계를 명시해주었다. 하지만 @ComponentScan을 사용할 때는 이런 설정 정보 자체가 사라지므로, 클래스 내부에서 직접적으로 명시를 해주어야한다.
그 역할을 하는 것이 바로 @AutoWired이다.
@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를 붙여주면, 스프링에서 의존관계를 자동으로 주입해준다.
💡Tip 의존 관계 주입 방식
의존 관계를 주입하는 방법에는 세 가지가 존재한다.
1. 생성자 주입
@Component
public class MemberServiceImpl implements MemberService {
private MemberRepository memberRepository;
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
2. setter 주입
@Component
public class MemberServiceImpl implements MemberService {
private MemberRepository memberRepository;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
3. 필드 주입
@Component
public class MemberServiceImpl implements MemberService {
@AutoWired
private MemberRepository memberRepository;
}
하지만 의존관계를 실행 중 동적으로 변경할 경우는 거의 없으므로 생성자 주입을 권장한다. 이것에 대한 더 자세한 설명은 바로 다음 Section에 나온다.
컴포넌트 스캔의 동작 과정
1. @ComponentScan
@Component가 붙은 모든 클래스를 스캔하여, 스프링 컨테이너에 등록한다. 이때 스캔의 대상은 따로 지정하지 않았다면, @ComponentScan이 붙은 설정 파일과 동일한 패키지와 그 하위 패키지가 된다.
위에서 언급했지만, 스프링 빈의 이름은 지정하지 않았을 시, 클래스 명에서 첫 문자만 소문자로 바꾸어 스프링 컨테이너에 등록한다.
💡 Tip 이름 부여하는 법
이름을 따로 지정하고 싶을 경우, @Component("AtraFelis")와 같이 지정해줄 수 있다. 하지만 이전 포스팅에서도 말했듯, 타당한 이유 없이 자의적으로 변경하는 것은 개발에 혼란을 불러올 수 있으므로 권장하지 않는다.
2. @AutoWired
스프링 빈에 등록을 했으므로, 이 스프링 빈 객체를 이용해 의존관계를 주입해준다. @AutoWired가 붙어 있는 생성자(혹은 필드, setter 메소드)를 찾아 스프링 컨테이너가 자동으로 주입해준다.
컴포넌트 스캔의 대상
@Component가 붙은 클래스가 컴포넌트 스캔의 대상이라고 하였다. 하지만, 이것이 이외에도 컴포넌트 스캔이 되는 애노테이션이 더 존재한다.
// Service
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service { ... }