김영한님의 Spring 핵심원리 강의를 듣고 정리하는 글입니다.
1. 서론
컴포넌트 스캔과 의존관계를 자동으로 주입받게 되는 과정에 대해서 다루었던 적이 있다. 이 포스팅에서는 의존관계를 주입하는 4가지 방법들에 대해서 다루겠다.
2. 의존관계 자동주입 방법 4가지
- 생성자 주입
- setter 주입
- 필드 주입
- 일반 메서드 주입
의존관계를 주입하는 방법에는 4가지가 있다. 하나씩 예제를 통해서 정리해보자.
의존관계를 주입할 때 기본적으로 @Autowired 어노테이션을 사용한다.
참고로 @Autowired는 빈을 찾아서 주입하게 되는데 주입할 대상이 없을 경우 에러가 발생한다. 주입할 대상이 없더라도 동작하게 하려면 @Autowired(required = false)로 지정하면 된다.
a. 생성자 주입
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired // 생성자가 1개만 있을 경우 생략 가능
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
...
}
// Lombok 적용
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy
...
}
생성자를 통해서 의존 관계를 주입 받는 방법이다.
- 생성자 호출 시점에 딱 1번만 호출되는 것을 보장하여, 불변, 필수 의존관계에 사용한다.
- 불변은 한 번 의존 관계를 맺고나면 변경할 수 없는 것이고, 필수는 반드시 의존 관계를 맺어야 한다는 뜻이다.
- 또한 생성자가 딱 1개만 있으면
@Autowired
생략해도 된다. final
키워드를 통해 외부에서 값을 설정할 수 없도록 컴파일 시점에 막아줄 수 있다.- Lombok을 통해서 간결하게 작성할 수 있다.
b. setter 주입
@Component
public class OrderServiceImpl implements OrderService {
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
...
}
setter를 통해 필드의 값을 변경하여 의존 관계를 주입하는 방법이다.
- 선택, 변경 가능성이 있는 의존 관계에 사용된다.
c.필드 주입
@Component
public class OrderServiceImpl implements OrderService {
@Autowired private MemberRepository memberRepository;
@Autowired private DiscountPolicy discountPolicy;
...
}
필드에서 바로 주입하는 방법이다.
- 코드가 매우 간결하지만 치명적인 단점 있기 때문에 사용하지 않는다.
- 외부에서 변경이 불가능 해서 테스트 하기가 어렵다.
- 별도의 생성자나 메서드가 없기 때문에 외부에서 의존 관계를 주입할 수 없어 DI 프레임워크 없이 아무것도 할 수 없다.
- 스프링 부트 통합 테스트에는 사용해도 좋다.
- 외부에서 변경이 불가능 해서 테스트 하기가 어렵다.
d. 일반 메서드 주입
@Component
public class OrderServiceImpl implements OrderService {
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
...
}
- 일반 메서드를 통해서 주입하는 방법이다.
- 한번에 여러 필드를 주입 받을 수 있다.
- 생성자, setter와 같은 방법으로 충분히 해결할 수 있기 때문에 일반 메서드 방식은 잘 사용하지 않는다.
3. 권장방식
생성자 주입 방식을 권장한다.
- 대부분의 의존 관계 주입은 한번 일어나면 애플리케이션 종료 시점까지 의존 관계를 변경할 일이 없다. 오히려 대부분의 의존 관계는 애플리케이션 종료 전까지 변하면 안된다.(불변해야 한다.)
- 수정자 주입의 경우 public으로 열어두게 되는데, 누군가 실수로 변경할 수 있기 때문에 좋은 설계 방법이 아니다.
- 테스트코드 작성 시에도 생성자에서 누락할 경우 컴파일 시점에서 오류를 알려주기 때문에 생성자 방식을 사용하는게 좋다.
- 이러한 걸 고려할 때 생성자 주입 방식이 가장 좋은 방법이다.
4. 옵션 처리
앞서 언급했듯이 주입 대상이 없을 경우에 대해서 어떻게 처리하는지 간단한 예시를 통해 정리해보자.
Member에 스프링 빈이 아니기 때문에 주입할 수 없다고 가정하자.
class TestBean {
@Autowired(required = false)
public void setNoBean1(Member noBean1) {
System.out.println("noBean1 = " + noBean1);
}
@Autowired
public void setNoBean2(@Nullable Member noBean2) {
System.out.println("noBean2 = " + noBean2);
}
@Autowired
public void setNoBean3(Optional<Member> noBean3) {
System.out.println("noBean3 = " + noBean3);
}
}
실행 결과
setNoBean2 = null
setNoBean3 = Optional.empty
- @Autowired(required = false)는 주입할 대상이 없을 경우에 호출 자체가 안된다.
- @Nullable을 통해 null로 처리한다.
- Optional을 통해 처리할 수 있다.
5. 마무리
- 의존 관계를 주입하는 방법에는 생성자 주입, setter 주입, 필드 주입, 일반 메서드 주입 방식이 있다. 하지만 생성자 방식을 권장한다.
- Lombok을 통해 더 간결한 코드로 작성하는 것도 좋은 방법이다.
'Programming > Spring' 카테고리의 다른 글
[Spring Boot] Spring Boot + Redis CRUD 예제 (0) | 2023.09.24 |
---|---|
[Spring Boot] Spring Integration MQTT 예제 (0) | 2023.09.24 |
[Spring] Bean 생명주기 콜백 (0) | 2023.09.16 |
[Spring] 컴포넌트 스캔(Component Scan) (0) | 2023.09.07 |
[Spring] DI, DL, IoC와 ApplicationContext (0) | 2023.09.04 |