이번 포스팅에서는 스프링의 의존관계 자동 주입에 대해서 알아보겠습니다.
다양한 의존관계 주입 방법
- 생성자 주입
- 수정자 주입
- 필드 주입
- 일반 메서드 주입
생성자 주입
- 생성자를 통해 의존 관계를 주입하는 방법입니다.
- 특징
1. 생성자 호출시점에 딱 1번만 호출되는 것이 보장됩니다.
2. 불변, 필수 의존관계에 사용됩니다.
@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;
}
}
위의 코드를 봤을 때 필드 값으로 MemberRepository와 DiscountPolicy를 선언해두고 생성자를 통해 의존관계를 주입한느 것을 볼 수 있습니다.
이때, @Autowired 애노테이션이 붙어있어서 스프링에게 의존관계 주입임을 알려줍니다.
** 생성자가 딱 1개만 있을 경우 @Autowired를 생략하더라도 자동 주입이 가능합니다.
수정자 주입(setter 주입)
- setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해 의존관계를 주입합니다.
- 특징
1. 선택, 변경 가능성이 있는 의존관계에서 사용합니다.
2. 자바빈 프로퍼티 규약의 수정자 메서드 방식을 사용합니다.
@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;
}
}
위의 코드를 봤을 때 setMemberRepository와 setDiscountPolicy를 통해 필드값들을 넣어줌으로써 의존관계를 주입하고 있습니다.
각각의 메소드에 @Autowired를 붙여줌으로써 스프링에게 의존관계 주입을 하는 것을 알려줍니다.
필드 주입
- 이름 그대로 필드에 바로 주입하는 방법입니다.
- 특징
1. 코드가 간결하지만 외부에서 변경이 불가능하고 이로 인해 테스트 하기 힘들다는 단점이 있습니다.
2. DI프레임워크가 없으면 아무것도 할 수 없는 코드가 됩니다.
3. 사용하는 것을 추천하지 않습니다.
4. 테스트 코드나 스프링 설정을 목적으로 하는 @Configuration에서만 사용한느 것을 추천합니다.
@Component
public class OrderServiceImpl implements OrderService {
@Autowired
private MemberRepository memberRepository;
@Autowired
private DiscountPolicy discountPolicy;
}
일반 메서드 주입
- 일반 메서드를 통해서 주입 받을 수 있습니다.
- 특징
1. 한번에 여러 필드를 주입 받을 수 있습니다.
2. 생성자주입과 수정자 주입에서 할 수 있는 부분이라 일반적으로 사용하지 않습니다.
@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;
}
}
옵션 처리
스프링을 이용하다 보면 스프링 빈이 없어도 동작해야 할때가 있다고 합니다.
저는 아직 경험이 적다 보니 그런 경우를 만나지 못했습니다...
이때는 @Autowired만 사용할 경우 required옵션의 기본값인 true가 되어 자동 주입 대상이 없다는 오류가 발생합니다.
자동 주입 대상일 때 옵션으로 처리한는 방법은 세가지가 있습니다.
- @Autowired(required=false) : 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출되지 않습니다.
- org.springframework.lang.@Nullable : 자동 주입할 대상이 없으면 null이 입력됩니다.
- Optional<>: 자동 주입할 대상이 없으면 Optional.empty가 입력됩니다.
//호출 안됨
@Autowired(required = false)
public void setNoBean1(Member member) {
System.out.println("setNoBean1 = " + member);
}
//null 호출
@Autowired
public void setNoBean2(@Nullable Member member) {
System.out.println("setNoBean2 = " + member);
}
//Optional.empty 호출
@Autowired(required = false)
public void setNoBean3(Optional<Member> member) {
System.out.println("setNoBean3 = " + member);
}
세가지 경우 모드 Member를 호출하지만 스프링 빈이 아니기 때문에 스프링이 자동으로 주입할 수 없는 상황입니다.
* 첫 번째 코드의 경우
- required를 false로 설정해 메서드가 호출되지 않습니다.
* 두 번째 코드의 경우
- @Nullable가 붙어 빈이 존재하지 않을경우 null이 들어갑니다.
* 세 번째 코드의 경우
- Optional을 이용해 주입할 대상이 없을 때 Optional.empty를 주입하게 됩니다.
그렇다면 의존관계 주입을 할때 무슨 방법을 사용해야 할까??
위에서 의존관계 주입을 할 때 4가지의 경우를 설명했습니다.
각각의 장점이 있는데 어떤 방법을 사용하는 것이 가장 합리적일까요?
결론부터 말하면 생성자 주입을 사용하는 것이 가장 좋습니다.
크게 불변과 누락의 시점으로 볼 수 있습니다.
불변
- 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없습니다. 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안됩니다. -> 불변
- 수정자 주입을 사용하게 되면 setXxx 메서드를 public로 열어둬야 합니다.
- public으로 열어두는 것은 누군가 실수로 값을 변경할 수 있고, 변경해서는 안되는 메서드를 열어 두는 것은 결코 좋은 설계가 아닙니다.
- 하지만 생성자 주입을 사용하면 객체를 생성할때 1번만 호출되므로 실수로 값을 변경할 일이 없어집니다.
누락
프레임워크 없이 순수한 자바 코드 단위의 테스트일 경우
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;
}
}
위에 코드 처럼 OrderServiceImpl클래스의 의존관계 주입을 위해 수정자 주입을 사용했습니다.
@Test
void createOrder() {
OrderServiceImpl orderService = new OrderServiceImpl();
orderService.createOrder(1L, "itemA", 10000);
}
위의 테스트 코드로 테스트를 진행할 경우 memberRepository와 discountPolicy의 의존관계 주입이 누락되었다는 오류가 발생합니다.
즉 테스트를 실행하기 전에는 어디서 오류가 나는지 알수 없습니다....
하지만 생성자 주입을 할 경우 컴파일 오류가 발생하고 오류를 바로 수정할 수있습니다.
final 키워드
- 생성자 주입을 사용하면 필드에 final키워드를 사용할 수 있고 생성자에서 혹시라도 누락되는 코드를 컴파일 시점에서 잡아줄 수 있습니다.
Lombok
lombok이라는 라이브러리를 사용해 생성자 주입을 할 때 조금더 편리하게 사용할 수 있습니다.
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
private private MemberRepository memberRepository;
private private DiscountPolicy discountPolicy;
/*
* 이전까지 하던 방법
@Autowired
public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
*/
}
위의 코드처럼 @RequiredArgsConstructor을 클래스에 붙여주고 필요한 클래스들을 선언할 때 final을 붙여주면 생성자를 작성하지 않아도 생성자 주입을 사용할 수 있습니다.
Lombok에 대해서 추가 설명을 한다면
@Getter, @Setter, @ToString 과 같이 xxGetter, xxSetter를 따로 만들지 않아도 만들어 주고 필드값들을 예쁘게 출력해주기도 합니다.
=> 애노테이션을 선언해줌으로써 자주 사용하는 코드를 줄일 수 있습니다.
출처
'스프링' 카테고리의 다른 글
스프링 시큐리티를 이용한 회원가입과 로그인 3 - html 화면 (0) | 2021.10.02 |
---|---|
스프링 시큐리티를 이용한 회원가입과 로그인 1 (0) | 2021.10.01 |
스프링 컨포넌트 스캔 (0) | 2021.09.15 |
스프링 싱글톤 (0) | 2021.09.14 |
스프링 컨테이너와 스프링 빈 (0) | 2021.09.13 |
댓글