조컴퓨터

의존관계(DI) 자동 주입 - 생성자 주입, 수정자 주입, 필드 주입 본문

공부/Spring

의존관계(DI) 자동 주입 - 생성자 주입, 수정자 주입, 필드 주입

챠오위 2022. 1. 20. 22:09

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

- 생성자 주입

- 수정자 주입(setter 주입)

- 필드 주입

- 일반 메서드 주입

 

 

생성자 주입

- 말그대로 생성자를 통해서 의존관계를 주입 받는 방법이다.

- 생성자 호출 시점에 딱 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;
    }
    
}

 

 

수정자 주입

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

- 선택, 변경 가능성이 있는 의존관계에 사용

- 자바빈 프로퍼티 규약의 수정자 메서드 방식을 사용하는 방법이다.

 

@Component
public class OrderServiceImpl implements OrderService {

    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

    @Autowired
    public void setMemberRepository(MemberRepository memberRepository) {
        System.out.println("memberRepository = " + memberRepository);
        this.memberRepository = memberRepository;
    }

    @Autowired
    public void setDiscountPolicy(DiscountPolicy discountPolicy) {
        System.out.println("discountPolicy = " + discountPolicy);
        this.discountPolicy = discountPolicy;
    }
    
}

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

 

 

필드 주입 

- 이름 그대로 필드에 바로 주입하는 방법이다.

- 외부에서 변경이 불가능해서 테스트하기에 용이하지 않다.

- DI 프레임워크가 없으면 아무것도 할 수 없다.

- 사용하지 말자!

 

- 단, 애플리케이션의 실제 코드와 관계 없는 테스트 코드나

- 스프링 설정을 목적으로 하는 @Configuration 같은 곳에서만 특별한 용도로 사용

 

@Component
public class OrderServiceImpl implements OrderService {

    @Autowired private MemberRepository memberRepository;
    @Autowired private DiscountPolicy discountPolicy;
    
}

 

순수한 자바 테스트 코드에는 당연히 @Autowired 가 동작하지 않는다. @SpringBootTest 처럼 스프링 컨테이너를 테스트에 통합한 경우에만 가능하다.

 

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

 

@Bean
OrderService orderService(MemberRepository memberRepository, DiscountPolicy discountPolicy) {

    return new OrderServiceImpl(memberRepository, discountPolicy);
    
}

 

 

일반 메서드 주입

- 일반 메서드를 통해서 주입 받을 수 있다.

- 한번에 여러 필드를 주입 받을 수 있다.

- 일반적으로 잘 사용하지 않는다.

 

@Component
public class OrderServiceImpl implements OrderService {

    private MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

    @Autowired	// 메서드 위에 @Autowired 있음
    public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
    
}

 

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

 

 

 

생성자 주입의 장점

- 순환 참조 방지

- final 선언이 가능

- 테스트 코드 작성에 용이

 

수정자 주입과 필드 주입은 빈을 먼저 생성한 후, 애노테이션이 붙은 필드에 해당하는 빈을 찾아 주입한다.

 

하지만, 생성자 주입 방식은 필드 주입이나 수정자 주입 방식과는 다르게 빈을 주입하는 순서에서 차이가 있다.

생성자 주입의 경우에는 먼저 생성자의 인자에 사용되는 빈을 찾거나 빈 팩토리에서 만들고, 이후에 찾은 인자 빈으로 주입하려는 빈의 생성자를 호출한다. 즉, 먼저 빈을 생성하지 않고 주입하려는 빈을 먼저 찾는다.

순서는 다음과 같다.

 

1. @ComponentScan 은 @Component 가 붙은 모든 클래스를 스프링 빈으로 등록하고,

2. 생성자에 @Autowired 를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다. 이때 생성자에 파라미터가 많아도 다 찾아서 자동으로 주입한다.

 

 

그렇기 때문에 생성자 주입에서만 종종 순환 참조가 실행되면서 문제가 발생한다.

(참고로 순환 참조 연결고리가 없는 디자인이 스프링이 추구하는 방식이다.)

객체 생성 시점에 빈을 주입하기 때문에 서로 참조하는 객체가 생성되지 않은 상태에서 그 빈을 참조하게 된다. 

이때 오류가 발생한다. 이것이 순환 참조 오류에 해당한다. 

 

의존관계 주입에 해당하는 사례는 아니지만 순환 참조에는 다음과 같은 예시도 있다. 

 

내가 가장 최근에 맞닥뜨린 순환참조의 예시에 해당한다. Config 문서를 target 에서 제외하는 것으로 해당 문제는 해결되었다.

 

 

참조)

1. 김명한 님의 '스프링 핵심 원리 - 기본편' 에서 다양한 의존 관계 주입 방법

2. 스프링 - 생성자 주입을 사용해야 하는 이유, 필드인젝션이 좋지 않은 이유 (yaboong.github.io)

 

스프링 - 생성자 주입을 사용해야 하는 이유, 필드인젝션이 좋지 않은 이유

개요 Dependency Injection (의존관계 주입) 이란 Setter Based Injection (수정자를 통한 주입) Constructor based Injection (생성자를 통한 주입) 스프링에서 사용할 수 있는 DI 방법 세가지 생성자 주입을 이용한 순

yaboong.github.io

 

3. Spring Boot JPA 활용1 (tistory.com)

 

Spring Boot JPA 활용1

Spring Boot JPA 활용 1 Field Injection보다 Constructor Injection을 사용하자. 스프링에서 등록된 빈을 사용하기 위한 3가지 DI(Dependency Injection) 방법을 제공한다. 필드 주입(Field Injection) 수정자..

juns-lee.tistory.com