본문 바로가기

SPRING FRAMEWORK

[Finally Spring] 2. DI 주입방법(생성자, 수정자, 필드, 일반 메서드)

 

요약

1) 4가지 DI 방법

생성자 주입 (Constructor)

수정자 주입 (Setter)

필드 주입 (Field)

일반 메서드 주입 (Method)

2) 가장 추천하는 DI 방법 (2가지)

생성자 주입(불변과 필수 의존 관계)

  • 생성자 호출 시점에 딱 1번만 호출되는 것이 보장됩니다.
  • '불변과 필수' 의존 관계에 사용됩니다.
  • 생성자가 1개만 존재하는 경우 @Autowired를 생략해도 자동 주입 합니다.  
  • NullPointerException 방지 가능합니다.
  • 주입받을 필드를 final로 선언 가능합니다.

수정자 주입(선택과 변경 의존관계)

  • '선택과 변경' 가능성이 있는 의존관계에 사용됩니다.
  • 자바빈 프로퍼티 규약(JAVA Bean Property Protocol)의 수정자 메서드를 사용하는 방법입니다.
  • 반드시 @Autowired를 입력해야합니다.

3) DI의 과정

(1) @Component가 클래스를 스프링 빈으로 등록합니다.

(2) 스프링 빈으로 등록한 다음 의존관계를 @Autowired 선언된 곳을 자동으로 주입합니다.

@Component // (1) 스프링 빈 등록: 해당 클래스를 스프링 컨테이너에 객체로 저장
public class OrderServiceImpl implements OrderService {
	
    // (2) 의존 객체 생성: 객체를 필드멤버로 선언
    private final UserRepository userRepository;
    private final DiscountInfo discountInfo;
    
    @Autowired // (3) 의존관계 형성: 생성자로 객체 의존성 주입
    public OrderServiceImpl (UserRepository userRepository, DiscountInfo discountInfo) {
    	this.userRepository = userRepository;
        this.discountInfo = discountInfo;
    }
}

4) DI 옵션 처리

주입할 스프링 빈이 없는 경우에도 동작해야 하는 경우가 있습니다.

(1) @Autowired만 사용하는 경우 required 옵션의 기본값이 true가 사용되어, 자동 주입 대상이 없으면 오류 발생

(2) 스프링 빈을 옵셔널(Optional)하게 해둔 상태에서 등록이 되지 않고, 기본 로직으로 동작하게 하는 경우

(3) 자동 주입 대상 옵션 처리 방법

  • @Autowired(required=false): 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출되지 않게 됩니다.
  • org.springframework.lang.@Nullable: 자동 주입할 대상이 없으면 null이 입력됩니다.
  • Optional<>: 자동 주입할 대상이 없으면 Optional.empty가 입력됩니다.

 


의존성 주입(Dependency Injection)의 개념에 대해 알아보았으니 실제 코드 상에서 의존성 주입을 구현하는 방법에 대해 알아보겠습니다. 의존성 주입은 총 4가지로 나누어집니다.

 

1. 생성자 주입 (Constructor)

2. 수정자 주입 (Setter)

3. 필드 주입 (Field)

4. 일반 메서드 주입 (method)

 

위 방법 중 실제 사용하는 방법은 2가지(생성자, 수정자) 입니다. 각 방법에 대해 알아보겠습니다. 

 

01. 생성자(Constructor)를 이용한 의존성 주입(DI)

생성자(Constructor)를 통해서 의존관계를 주입받는 방법입니다.

생성자에 @Autowired 애너테이션을 적용하면 스프링 컨테이너에서 @Component로 등록된 빈에서 생성자에 빈을 주입해줍니다. DI 과정을 정리하면

 

1. @Component가 클래스를 스프링 빈으로 등록합니다.

2. 스프링 빈으로 등록한 다음 의존관계를 @Autowired 선언된 곳을 자동으로 주입합니다.

 

@Component // (1) 스프링 빈 등록: 해당 클래스를 스프링 컨테이너에 객체로 저장
public class OrderServiceImpl implements OrderService {
	
    // (2) 의존 객체 생성: 객체를 필드멤버로 선언
    private final UserRepository userRepository;
    private final DiscountInfo discountInfo;
    
    @Autowired // (3) 의존관계 형성: 생성자로 객체 의존성 주입
    public OrderServiceImpl (UserRepository userRepository, DiscountInfo discountInfo) {
    	this.userRepository = userRepository;
        this.discountInfo = discountInfo;
    }
}

 

DI 방법 중 가장 많이 쓰이는 방법으로, 주요한 특징이 있습니다. 

 

1. 생성자 호출 시점에 딱 1번만 호출되는 것이 보장됩니다.

2. '불변과 필수' 의존 관계에 사용됩니다.

3. 생성자가 1개만 존재하는 경우 @Autowired를 생략해도 자동 주입 합니다.  

4. NullPointerException 방지 가능합니다.

5. 주입받을 필드를 final로 선언 가능합니다.

 

특히 객체가 여러 번 호출 될 염려가 없어 참조관계가 선명해지는 장점이 있어, 스프링에서는 생성자를 활용한 DI를 가장 추천합니다.

 

생성자가 1개 일 때는 @Autowired가 필요없는 이유는 무엇일까요? 스프링이 클래스의 객체를 생성하여 빈에 넣어야 하는데, 객체를 생성할 때 생성자를 반드시 부를 수 밖에 없습니다. 따라서 빈을 등록하면서 이미 의존관계 주입도 같이 발생하는 것이죠. 다만, 생성자가 2개 이상인 경우는 반드시 @Autowired로 선언해 알려주어야 합니다.

 

02. 수정자(Setter)를 이용한 의존성 주입(DI)

Setter라 불리는 필드의 값을 변경하는 수정자 메서드를 통해 의존 관계를 주입하는 방법입니다. 

 

1. '선택과 변경' 가능성이 있는 의존관계에 사용됩니다.

2. 자바빈 프로퍼티 규약(JAVA Bean Property Protocol)의 수정자 메서드를 사용하는 방법입니다.

3. 반드시 @Autowired를 입력해야합니다.

 

@Component // (1) 스프링 빈 등록: 스프링 컨테이너에 객체로 저장
public class OrderServiceImpl2 implements OrderService {
    // (2) 의존 객체 생성: 필드멤버로 의존할 객체를 불러옵니다. 
    private UserRepository userRepository;
    private DiscountInfo discountInfo;
    
    @Autowired // (3) 의존관계 주입: Setter 메서드로 객체를 주입합니다. 
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Autowired // (3) 의존관계 주입: Setter 메서드로 객체를 주입합니다. 
    public void setDiscountInfo(DiscountInfo discountInfo) {
        this.discountInfo = discountInfo;
    }
}

 

생성자 주입방식과는 달리 "set + 주입 객체" 형태로 메서드를 만들어 객체 의존성을 주입합니다.  또한 @Autowired를 입력하지 않으면 실행이 되지 않죠. 즉, 스프링 빈으로 등록할 때(@Component) 의존관계를 선언해 반드시 알려주어야 합니다. 

 

03. 필드(Field)를 이용한 의존성 주입(DI)

필드에 @Autowired를 붙여 바로 주입하는 방법

@Service
public class OrderServiceImpl implements OrderService{

	@Autowired
    private UserRepository userRepository;
    @Autowired
    private DiscountInfo discountInfo;
}

 

앞선 예제들과는 달리 필드멤버로 불러온 객체를 바로 주입했습니다. 굉장히 편해 보이는데, 이 방법은 추천하지 않는 방법입니다. 특징을 살펴보시죠.

 

특징

1. 코드가 간결하지만, 외부에서 변경이 불가능해 테스트하기 힘들다는 단점이 존재합니다.

2. DI 프레임워크가 없으면 작동하지 않습니다.

3. 실제 코드와 상상관 없는 특정 테스트 용도로는 사용 가능합니다.

4. 정상 작동을 위해서는 Setter가 필요하기 때문에 수정자 주입을 사용하는게 편리합니다.

 

04. 일반 메서드(Method)를 이용한 의존성 주입(DI)

일반 메서드 위에 @Autowired를 선언해 주입하는 방법입니다. 한번에 여러 필드를 주입받을 수 있는 특징을 지니고 있지만 일반적으로 사용되지 않는다고 합니다.(.....왜죠?)