[Spring Boot] 댜양한 의존 관계 주입 DI 방법

2021. 7. 1. 14:23프로그래밍/web

스프링을 사용하면서 DI를 사용하는 방법에는 크게 4가지가 있는데 각각의 장단점과 사용 방법에 대해서 알아보자.

 

1. 생성자 주입

의존성 주입을 생성자에서 수행하는 방법. 
생성자는 일반적으로 객체가 생성될때 딱 한번 실행되는 것이 보장된다.
불변 / 필수 의존관계 설정에 사용된다.

@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를 final로 선언해서, 객체가 생성될때 딱 한 번만 설정되고 뒤에 변경이 없음을 확인할 수 있다. 
위의 예제에서는 memberRepository와 discountPolicy가 항상 null이 아님을 확인 할 수있다.
추가로 스프링이 관리하는 container일 경우, 생성자가 한개인 경우에는 @Autowired 어노테이션을 붙이지않아도 자동으로 으로 등록해준다.

2. setter 주입

setter함수를 따로 만들어서 의존관계 주입을 수행하는 방법.
변경 가능성이 존재하는 의존관계에서 사용한다.

@Component
public class OrderServiceImpl implements OrderService{
    private MemberRepository memberRepository;
    
    @Autowired
    public void setMemberRepository(MemberRepository memberRepository){
    	this.memberRepository = memberRepository;
    }
}

추후에 OrderService.setMemberRepository(memberRepository)함수를 통해 의존성 주입 객체를 변경할 수도 있으나, 권장되는 방법은 아니다.

3. 필드 주입

field 자체에 DI를 적용하는 방법.
주로 테스트 코드에서나,외부에서 변경이 어려워서, 추가로 setter를 만들어줘야한다는 단점이 존재한다.

@Component
public class OrderServiceImpl implements OrderService{
    @Autowired private final MemberRepository memberRepository;
    @Autowired private final DiscountPolicy discountPolicy ;
}

자동으로 component scan해서 bean을 등록하는 spring container가 아닌, 아닌 순수한 자바 코드의 경우 테스트 할 수 있는 방법이 없다. NullpointerException이 발생한다.
나중에 다른 코드에서 사용할 일이 없는 TEST코드에서나 configuration에서 간편하게 사용된다.

4. 일반 함수 주입

아무 함수 위에 @Autowired를 붙이면 해당 함수에서 생성자 주입이 가능하다.
한번에 여러 필드를 의존성 주입할 수 있는 장점이 있지만, 생성자 주입과 setter를 주로 사용하고, 일반 함수 주입은 주로 사용하지 않는다. 

@Component
public class OrderServiceImpl implements OrderService{
    private MemberRepository memberRepository;
    
    @Autowired
    public void init(MemberRepository memberRepository){
    	this.memberRepository = memberRepository;
    }
}

 

생성자 주입이 권장되는 이유


생성자 주입을 사용하면, 프로그래머의 실수를 크게 줄일 수 있다.

1. 의존 관계들을 누락하는 경우를 막을 수있다.
   

@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;

// 생성자 주입의 경우 컴파일 에러 발생
// setter 주입인 경우 컴파일 에러를 발생시키지 않지만 돌려보면 에러 발생
OrderServiceImpl orderService = new OrderServiceImpl();

위와 같이 작성한 경우 setter 주입의 경우 컴파일 에러가 발생하지 않아, 직관적이지 못하다.
 
2. final을 통해, 초기화 단계에서 dependency를 할당하지 않는 문제를 예방할 수도있다.

@Component
public class OrderServiceImpl implements OrderService{
    // final은 생성자에서나, 초기 지정으로만 할당이 가능하다
    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy ;
	
    // 에러 발생
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
    }
 }

프로그래머가 실수로 생성자에 di를 하지 않는 경우가 발생할 수 도있는데, 이런 경우 final로 선언이 되어있으면 컴파일 에러가 발생한다.

 

결론

생성자 주입은 프로그래머의 실수가 발생한 경우 compile 에러를 발생시킨다.
compile 에러는 짱이다.
생성자 주입 짱짱