컴포넌트 스캔이란?
지금까지는 @Configuration이 붙은 설정 파일을 이용하여 빈을 수동 주입하였다. 이렇게 개발자가 수동으로 빈을 주입하고 의존 관계를 정해줄 수도 있지만, 만약 등록할 빈이 많다면 일일이 모두 등록해야 해서 상당히 귀찮다. 그래서 스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공한다. 또 의존관계도 자동으로 주입하는 @Autowired라는 기능도 제공한다.
@Component
public class OrderServiceImpl implements OrderService {
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);
return new Order(memberId, itemName, itemPrice, discountPrice);
}
}
@Component
public class RateDiscountPolicy implements DiscountPolicy {
private int discountPercent = 10;
@Override
public int discount(Member member, int price) {
if (member.getGrade() == Grade.VIP){
return price * discountPercent/100;
}else {
return 0;
}
}
}
@Configuration
@ComponentScan
public class AppConfig {
}
먼저, 위와 같이 빈으로 등록할 클래스에 @Component를 붙여 주고 설정 파일에 @Configuration과 @ComponentScan을 붙여준다. 그러면 끝난다. @ComponentScan이 @Component가 붙은 모든 객체를 찾아서 빈으로 등록하기 때문이다.
참고
@Configuration 이 컴포넌트 스캔의 대상이 된다. 이유는 @Configuration 소스코드를 열어보면 @Component 애노테이션이 붙어있기 때문이다.
컴포넌트 스캔 기본 대상
컴포넌트 스캔은 @Component 뿐만 아니라 다음과 내용도 추가로 대상에 포함한다.
@Component : 컴포넌트 스캔에서 사용
@Controlller : 스프링 MVC 컨트롤러에서 사용
@Service : 스프링 비즈니스 로직에서 사용
@Repository : 스프링 데이터 접근 계층에서 사용
@Configuration : 스프링 설정 정보에서 사용
의존 관계 자동 주입
컴포넌트 스캔으로 빈을 등록하고 나면 의존 관계를 만들어 줘야 한다. 설정 파일에서 수동으로 의존 관계를 주입할 때는 직접 주입할 코드를 적어 주었지만, 자동으로 빈 등록할 때는 @Autowired라는 어노테이션을 사용해야 한다.
이때, @Autowired를 사용하여 의존 관계를 주입하는 방법은 총 5가지가 있습니다.
(1) 생성자 주입
이름 그대로 생성자를 통해서 의존 관계를 주입받는 방법이다.
최근 스프링 버전부터는 생성자가 1개만 존재한다면 @Autowired를 생략해도 무방하게끔 업데이트되었다.
@Component
public class OrderServiceImpl implements OrderService {
private final DiscountPolicy discountPolicy;
@Autowired //생략가능 -> 생성자가 1개이기때문이다.
public OrderServiceImpl(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);
return new Order(memberId, itemName, itemPrice, discountPrice);
}
}
위와 같이 일반적인 자바 코드에서 사용하는 것과 똑같이 사용한다. 생성자 주입은 생성자 호출 시점에 딱 1번만 호출된다는 것이 보장되기 때문에 불변 또는 필수 의존 관계에 사용된다. @Autowired는 스프링 빈이 아니면 작동하지 않습니다.
(2) 수정자 주입
setter를 통해 의존 관계를 주입하는 방법이다. 주로 선택이나 변경 가능성이 있는 의존 관계에 사용된다.
@Component
public class OrderServiceImpl implements OrderService {
private DiscountPolicy discountPolicy;
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);
return new Order(memberId, itemName, itemPrice, discountPrice);
}
@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy){
this.discountPolicy = discountPolicy;
}
}
위와 같이 setter 위의 @Autowired를 붙여줌으로써 의존 관계를 주입할 수 있다.
(3) 필드 주입
이름 그대로 필드에 바로 주입하는 방법입니다.
@Component
public class OrderServiceImpl implements OrderService {
@Autowired
private DiscountPolicy discountPolicy;
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);
return new Order(memberId, itemName, itemPrice, discountPrice);
}
}
생성자도 setter 코드 없이 상당히 간결하게 의존 관계를 주입할 수 있다는 장점이 있다. 하지만, 위 방법은 수많은 문제점이 존재한다.
먼저, 외부에서 변경이 불가능하므로 테스트를 하기 힘들다. 그리고 @Autowired라는 DI 프레임워크가 없다면 어떠한 것도 할 수 없다. 예를 들어 해당 코드에서 스프링 프레임워크를 제거하면 동작하지 않게 된다. 반면, 생성자나 setter는 @Autowired가 없다고 해서 코드가 먹통이 되는 일은 발생하지 않는다.
따라서 해당 방법은 프로덕션 코드에서는 사용하지 말고 테스트 코드에서만 사용해야 한다.
(4) 일반 메서드 주입
setter가 아닌 일반 메서드를 통해서 의존 관계를 주입하는 방법이다. setter와 달리 한 번에 여러 필드를 주입받을 수 있다는 장점이 있으나 일반적으로 잘 사용하지는 않는다.
@Component
public class OrderServiceImpl implements OrderService {
private final DiscountPolicy discountPolicy;
private final MemberRepository memberRepository;
@Autowired
public void init(DiscountPolicy discountPolicy, MemberRepository memberRepository){
this.discountPolicy = discountPolicy;
this.MemberRepository = memberRepository;
}
}
(5) lombok을 사용한 주입
최근에는 생성자를 딱 1개 두고, @Autowired를 생략하는 방법을 주로 사용한다. 여기에 Lombok 라이브러리의 @RequiredArgsConstructor 함께 사용하면 기능은 다 제공하면서, 코드는 깔끔하게 사용할 수 있다.
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
}
출처
'Spring' 카테고리의 다른 글
Spring Security + JWT 회원가입, 로그인 기능 구현 (7) | 2023.01.17 |
---|---|
빈의 생명주기 (1) | 2023.01.03 |
스프링 컨테이너와 스프링 빈 (3) | 2023.01.01 |
Spring Boot 동작 원리 (4) | 2022.12.24 |
스프링의 핵심 (5) | 2022.12.24 |