개발일지/스프링

DI(의존성 주입) 방법

티에리앙리 2022. 5. 6. 15:52

의존관계 주입

의존관계 주입은 크게 4가지 방법이 있다.

  • 생성자 주입
  • 수정자 주입(Setter 주입)
  • 필드 주입
  • 일반 메서드 주입

 

생성자 주입

  • 생성자를 통해서 의존 관계를 주입 받는 방법이다.
  • 특징
    • 생성자 호출시점에 딱 1번만 호출되는 것을 보장한다.
    • 불변, 필수 의존관계에 사용
  • 생성자가 딱 1개만 있으면 @Autowired를 생략해도 자동 주입 된다. 물론 스프링 빈에만 해당한다.
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;

//@Autowired  // 생성자가 하나이기 때문에 @Autowired 를 생략 가능하다.
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}

 

수정자 주입

  • setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입하는 방법이다.
  • 특징
    • 선택, 변경 가능성이 있는 의존관계에 사용
    • 자바빈 프로퍼티 규약의 수정자 메서드 방식을 사용하는 방법이다
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;

@Autowired
public void setMemberRepository(MemberRepository memberRepository){
    this.memberRepository = memberRepository;
}

@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy){
    this.discountPolicy = discountPolicy;
}

참고: @Autowired 의 기본 동작은 주입할 대상이 없으면 오류가 발생한다. 주입할 대상이 없어도 동작하게 하려면 @Autowired(required = false) 로 지정하면 된다.

참고: 자바빈 프로퍼티, 자바에서는 과거부터 필드의 값을 직접 변경하지 않고, setXxx, getXxx 라는 메서드를 통해서 값을 읽거나 수정하는 규칙을 만들었는데, 그것이 자바빈 프로퍼티 규약이다. 더 자세한 내용이 궁금하면 자바빈 프로퍼티로 검색해보자.

자바빈 프로퍼티 규약 예시

class Data {
	private int age;
	public void setAge(int age) {
		this.age = age;
	}
	public int getAge() {
		return age;
	}
}

 

필드 주입

  • 이름 그대로 필드에 바로 주입하는 방법이다.
  • 특징
    • 코드가 간결해서 많은 개발자들을 유혹하지만 외부에서 변경이 불가능해서 테스트 하기 힘들다는치명적인 단점이 있다.
    • DI 프레임워크가 없으면 아무것도 할 수 없다. 사용하지 말자!
    • 애플리케이션의 실제 코드와 관계 없는 테스트 코드
    • 스프링 설정을 목적으로 하는 @Configuration 같은 곳에서만 특별한 용도로 사용
@Autowired private MemberRepository memberRepository;
@Autowired private DiscountPolicy discountPolicy;

참고: 순수한 자바 테스트 코드에는 당연히 @Autowired가 동작하지 않는다. @SpringBootTest 처럼

스프링 컨테이너를 테스트에 통합한 경우에만 가능하다.

참고: 다음 코드와 같이 @Bean 에서 파라미터에 의존관계는 자동 주입된다. 수동 등록시 자동 등록된 빈의 의존관계가 필요할 때 문제를 해결할 수 있다

 

일반 메서드 주입

  • 일반 메서드를 통해서 주입 받을 수 있다.
  • 특징
    • 한번에 여러 필드를 주입 받을 수 있다.
    • 일반적으로 잘 사용하지 않는다.
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
 
@Autowired
public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
	this.memberRepository = memberRepository;
	this.discountPolicy = discountPolicy;
}

참고 : 당연한 이야기이지만 의존관계 자동 주입은 스프링 컨테이너가 관리하는 스프링 빈이어야 동작한다. 스프링 빈이 아닌 Member 같은 클래스에서 @Autowired 코드를 적용해도 아무 기능도 동작하지 않는다

 

 

필드 주입은 의존성을 주입하기가 쉽지만 그만큼 하나의 class가 많은 책임을 떠안아야할 수 있다는 위험성이 있습니다. 또 숨은 의존성만을 제공하고, 필요한 의존성을 가진 class를 곧바로 인스턴스화 시킬 수 없으며 final을 선언할 수 없습니다. 그래서 필드 주입은 읽기 쉽고 사용하기 편하다는 것 외에는 큰 장점이 없는 것으로 알려져 있습니다. setter 주입은 선택적인 의존성을 사용할 때 유용합니다. 상황에 따라 주입할 수 있다는 장점이 있지만 주입이 필요한 객체가 주입이 되지 않아도 얼마든지 객체를 생성할 수 있다는 문제점이 있습니다. service 구현체를 주입해주지 않아도 controller 객체 생성이 가능하여, 내부에 있는 service의 method 호출이 가능해집니다. set을 통해 service의 구현체를 주입해주지 않았으므로 NPE가 발생할 수 있습니다. 이를 해결한 것이 생성자 주입입니다. 스프링 프레임워크 에서 권장하는 방법이기도 합니다. 필수적으로 사용해야하는 의존성 없이는 인스턴스를 만들지 못하게 해놓았기 때문입니다. 또, null을 주입하지 않는이상 NPE가 발생하지 않고, 순환 의존성을 알 수 있고, final 선언이 가능하며, 의존관계를 주입하지 않으면 필드주입이 가진 단점을 모두 보완할 수 있습니다. 하지만 생성자가 많아지면 코드가 길어질 수 있으므로 SRP 원칙을 생각해 리팩토링을 할 수 있습니다. 보통 더 좋은 디자인 패턴과 코드 품질을 위해서는 생성자 주입을 많이 사용하는 편입니다.