본문 바로가기
스프링

스프링과 의존관계 자동 주입

by 근즈리얼 2021. 9. 20.
728x90

이번 포스팅에서는 스프링의 의존관계 자동 주입에 대해서 알아보겠습니다.

 

다양한 의존관계 주입 방법
  • 생성자 주입
  • 수정자 주입
  • 필드 주입
  • 일반 메서드 주입

생성자 주입

- 생성자를 통해 의존 관계를 주입하는 방법입니다.

- 특징

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

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

@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를 선언해두고 생성자를 통해 의존관계를 주입한느 것을 볼 수 있습니다.

이때, @Autowired 애노테이션이 붙어있어서 스프링에게 의존관계 주입임을 알려줍니다.

** 생성자가 딱 1개만 있을 경우 @Autowired를 생략하더라도 자동 주입이 가능합니다.

 

수정자 주입(setter 주입)

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

- 특징

1. 선택, 변경 가능성이 있는 의존관계에서 사용합니다.

2. 자바빈 프로퍼티 규약의 수정자 메서드 방식을 사용합니다.

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

위의 코드를 봤을 때 setMemberRepository와 setDiscountPolicy를 통해 필드값들을 넣어줌으로써 의존관계를 주입하고 있습니다.

각각의 메소드에 @Autowired를 붙여줌으로써 스프링에게 의존관계 주입을 하는 것을 알려줍니다.

 

필드 주입

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

- 특징

1. 코드가 간결하지만 외부에서 변경이 불가능하고 이로 인해 테스트 하기 힘들다는 단점이 있습니다.

2. DI프레임워크가 없으면 아무것도 할 수 없는 코드가 됩니다.

3. 사용하는 것을 추천하지 않습니다.

4. 테스트 코드나 스프링 설정을 목적으로 하는 @Configuration에서만 사용한느 것을 추천합니다.

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

 

일반 메서드 주입

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

- 특징

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

2. 생성자주입과 수정자 주입에서 할 수 있는 부분이라 일반적으로 사용하지 않습니다.

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

 

옵션 처리

스프링을 이용하다 보면 스프링 빈이 없어도 동작해야 할때가 있다고 합니다.

저는 아직 경험이 적다 보니 그런 경우를 만나지 못했습니다...

 

이때는 @Autowired만 사용할 경우 required옵션의 기본값인 true가 되어 자동 주입 대상이 없다는 오류가 발생합니다.

 

자동 주입 대상일 때 옵션으로 처리한는 방법은 세가지가 있습니다.

  • @Autowired(required=false) : 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출되지 않습니다.
  • org.springframework.lang.@Nullable : 자동 주입할 대상이 없으면 null이 입력됩니다.
  • Optional<>: 자동 주입할 대상이 없으면 Optional.empty가 입력됩니다.
//호출 안됨
@Autowired(required = false)
public void setNoBean1(Member member) {
    System.out.println("setNoBean1 = " + member);
}

//null 호출
@Autowired
public void setNoBean2(@Nullable Member member) {
    System.out.println("setNoBean2 = " + member);
}

//Optional.empty 호출
@Autowired(required = false)
public void setNoBean3(Optional<Member> member) {
    System.out.println("setNoBean3 = " + member);
}

세가지 경우 모드 Member를 호출하지만 스프링 빈이 아니기 때문에 스프링이 자동으로 주입할 수 없는 상황입니다.

 

* 첫 번째 코드의 경우

- required를 false로 설정해 메서드가 호출되지 않습니다.

 

* 두 번째 코드의 경우

- @Nullable가 붙어 빈이 존재하지 않을경우 null이 들어갑니다.

 

* 세 번째 코드의 경우

- Optional을 이용해 주입할 대상이 없을 때 Optional.empty를 주입하게 됩니다.

 

 

그렇다면 의존관계 주입을 할때 무슨 방법을 사용해야 할까?? 

위에서 의존관계 주입을 할 때 4가지의 경우를 설명했습니다. 

각각의 장점이 있는데 어떤 방법을 사용하는 것이 가장 합리적일까요?

 

결론부터 말하면 생성자 주입을 사용하는 것이 가장 좋습니다.

크게 불변과 누락의 시점으로 볼 수 있습니다.

 

불변

- 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없습니다. 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안됩니다. -> 불변

- 수정자 주입을 사용하게 되면 setXxx 메서드를 public로 열어둬야 합니다.

- public으로 열어두는 것은 누군가 실수로 값을 변경할 수 있고, 변경해서는 안되는 메서드를 열어 두는 것은 결코 좋은 설계가 아닙니다.

- 하지만 생성자 주입을 사용하면 객체를 생성할때 1번만 호출되므로 실수로 값을 변경할 일이 없어집니다.

 

누락

프레임워크 없이 순수한 자바 코드 단위의 테스트일 경우

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

위에 코드 처럼 OrderServiceImpl클래스의 의존관계 주입을 위해 수정자 주입을 사용했습니다.

@Test
void createOrder() {
    OrderServiceImpl orderService = new OrderServiceImpl();
    orderService.createOrder(1L, "itemA", 10000);
}

위의 테스트 코드로 테스트를 진행할 경우 memberRepository와 discountPolicy의 의존관계 주입이 누락되었다는 오류가 발생합니다.

즉 테스트를 실행하기 전에는 어디서 오류가 나는지 알수 없습니다....

하지만 생성자 주입을 할 경우 컴파일 오류가 발생하고 오류를 바로 수정할 수있습니다.

 

final 키워드

- 생성자 주입을 사용하면 필드에 final키워드를 사용할 수 있고 생성자에서 혹시라도 누락되는 코드를 컴파일 시점에서 잡아줄 수 있습니다.

 

Lombok

lombok이라는 라이브러리를 사용해 생성자 주입을 할 때 조금더 편리하게 사용할 수 있습니다.

@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
    
    private private MemberRepository memberRepository;
    private private DiscountPolicy discountPolicy;
    
    /*
    * 이전까지 하던 방법
    @Autowired
    public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
    */
}

위의 코드처럼 @RequiredArgsConstructor을 클래스에 붙여주고 필요한 클래스들을 선언할 때 final을 붙여주면 생성자를 작성하지 않아도 생성자 주입을 사용할 수 있습니다.

 

Lombok에 대해서 추가 설명을 한다면

@Getter, @Setter, @ToString 과 같이 xxGetter, xxSetter를 따로 만들지 않아도 만들어 주고 필드값들을 예쁘게 출력해주기도 합니다.

=> 애노테이션을 선언해줌으로써 자주 사용하는 코드를 줄일 수 있습니다.

 

출처

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

 

스프링 핵심 원리 - 기본편 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 개발자가 되어보세요! 📣 확인해주

www.inflearn.com

 

728x90

댓글