티스토리 뷰

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

 

1. 생성자 주입(Constructor)

2. 수정자 주입(Setter)

3. 필드 주입

4. 일반 메서드 주입


생성자 주입

생성자 주입은 말 그대로 생성자를 통해서 의존관계를 주입 받는 방법이다. 생성자 호출시점에 딱 1번만 호출되는 것이 보장된다. 불변, 필수 의존관계에 사용한다. 앞에서 설명했던 OrderServiceImpl 클래스를 보면, 클래스 내부 멤버 변수들이 private final로 선언된 것을 볼 수 있다. final로 선언된 멤버 변수들은 반드시 생성자 주입을 통해 생성자 호출시점에 딱 1번 호출되어야 한다. final로 선언된 변수는 값을 절대로 변경할 수 없기 때문에 최초에 생성되는 시점에 정의한다.

 

생성자가 1개 존재하는 경우에는 @Autowired를 생략해도 된다.

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

 

 

수정자 주입

setter라고 불리는 필드의 값을 변경하는 수정자 함수를 통해서 의존관계를 주입하는 방법. 생성자 주입은 불변, 필수 의존관계에 사용했지만, 수정자 주입은 선택, 변경 가능성이 있는 의존관계에 사용한다. 생성자 주입과는 다르게 멤버 변수들의 값이 바뀔 수 있으므로 멤버 변수를 final로 선언하면 안된다. 생성자는 별도로 선언하지 않고 Default Constructor만 남겨두고(따로 작성하지 않아도 컴파일러가 자동으로 만들어준다) 멤버 변수마다 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;
     }
}

 

 

필드 주입

필드 주입은 말 그대로 필드(멤버 변수)에 바로 주입하는 방법이다. 코드가 간결해서 좋아보이지만 외부에서 변경이 불가능해 테스트 하기 힘들다는 치명적인 단점이 있다. DI 프레임워크가 없으면 아무것도 할 수 없기 때문에 사용하지 않는다고 생각하면 편하다.

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

 

일반 메서드 주입

일반 메서드를 통해서 주입 받을 수 있다. 한번에 여러 필드를 주입 받을 수 있는 것이 특징이지만 잘 사용하지 않는다.

아래의 코드 블럭을 보면 init 함수를 선언하고 인자로 MemberRepository 객체, DiscountPolicy 객체를 받아와 해당 멤버의 멤버 변수에 주입하는 것을 볼 수 있다.

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

크게 4가지 방법을 살펴보았는데 어떤 방법을 사용할지 결론적으로 딱 나온다.

생성자 주입을 사용하자 !

생성자 주입을 대부분이 사용하는 이유를 알아보면 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없기 때문이다. 오히려 의존관계가 애플리케이션이 작동하는 동안 바뀌는 것은 바람직하지 않을지도 모른다. 수정자 주입(setter)를 이용한 방식은 외부에 public으로 멤버 함수를 오픈해야 하기 때문에 누군가 실수로 변경할 수도 있다.

 

생성자 주입을 사용할 때 final 키워드를 반드시 사용하자. 외부에서 생성자 주입을 할 때 누락되는 값이 있거나, 코드를 작성할 때 값을 잘못설정하거나 변경하려는 움직임이 생기는 경우 컴파일 에러로 바로 잡아버릴 수 있다. 개발 과정에서 컴파일 오류는 가장 사랑하는 오류이다 !

 

또 생성자 주입을 선택하면 프레임워크에 의존하지 않고 순수한 자바 언어의 특징을 잘 살려낸다. 기본적으로 생성자 주입을 사용하고, 필수 값이 아닌 경우에는 수정자 주입 방식을 옵션으로 부여할 수 있다. 


여기까지 살펴보면 스프링 부트가 제공하는 기능이 별로 없다고 생각할 수도 있다. 막상 개발을 해보면 대부분이 불변이고 생성자에 final 키워드를 사용하게 된다. But.... 생성자를 만들고, 주입 받은 값을 대입하는 코드도 만들어야 하고... 개발자들은 귀찮은 것을 정말 싫어한다. (vi를 써보면 마우스 하나도 건들기 싫다는 굳은 의지를 볼 수 있다 ㅎㅎ). 그래서 스프링 부트가 제공하는 것이 '롬복'이다.

 

롬복은 스프링 부트가 제공하는 라이브러리이다. 롬복 라이브러리를 적용하는 방법 build.gradle에 다음과 같이 코드를 추가하면 된다.

//lombok 설정 추가 시작
configurations {
     compileOnly {
     	extendsFrom annotationProcessor
     }
}
//lombok 설정 추가 끝

dependencies {
 implementation 'org.springframework.boot:spring-boot-starter'
 
 	 //lombok 라이브러리 추가 시작
     compileOnly 'org.projectlombok:lombok'
     annotationProcessor 'org.projectlombok:lombok'
     
     testCompileOnly 'org.projectlombok:lombok'
     testAnnotationProcessor 'org.projectlombok:lombok'
 	 //lombok 라이브러리 추가 끝
 
     testImplementation('org.springframework.boot:spring-boot-starter-test') {
     	exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
 	}
 }

 

롬복 라이브러리를 적용한 후 의존관계를 주입할 클래스 상단에 @RequiredArgsConstructor 를 선언하면 생성자를 선언해 주입하는 코드를 자동으로 만들어준다. 위, 아래 코드는 모두 동일한 코드이다.

@Component
@RequiredArgsConstructor
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;
}

 

댓글