[Spring] 의존관계 자동 주입

jy9922·2022년 8월 25일
0

Spring

목록 보기
30/34
post-thumbnail

다양한 의존관계 자동 주입 💼

  • 의존관계 주입 방법은 크게 4가지가 있다.
    • 생성자 주입
    • 수정자 주입 (setter 주입)
    • 필드 주입
    • 일단 메서드 주입

✅ 생성자 주입

  • 생성자를 통해서 의존 관계를 주입 받는 방법

    특징

    • 생성자 호출시점에 딱 1번만 호출되는 것이 보장된다.
    • 불변, 필수 의존관계에 사용한다.
    @Component
    public class OrderServiceImp implements OrderService{
    
        // final 키워드 붙으면 무조건 값이 있어야 한다는 것이다. (필수)
        private final MemberRepository memberRepository;
        private final DiscountPolicy discountPolicy;
    
        // 생성자가 딱 1번만 호출된다. (불변)
        // 외부에서 수정할 수 없다.
        @Autowired
        public OrderServiceImp(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
            this.memberRepository = memberRepository;
            this.discountPolicy = discountPolicy;
        }
    

🚨 생성자가 딱 1개만 있으면 @Autowired를 생략해도 자동 주입된다! 물론 스프링 빈에만 해당된다. 🚨

@Component
public class OrderServiceImp implements OrderService{

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    // @Autowired 생략
    public OrderServiceImp(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }

✅ 수정자 주입 - setter 주입

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

    특징

    • 선택, 변경 가능성이 있는 의존관계에 사용한다.
    • 자바빈 프로퍼티 규약의 수정자 메서드 방식을 사용하는 방법이다.

    자바빈 프로퍼티란?

    자바에서 setXxx, getXxx 라는 메서드를 통해 값을 읽거나 수정하는 규칙을 말한다.

@Component
public class OrderServiceImp 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;
    }

OrderServiceImp를 스프링 컨테이너에 등록을 하고, 연관 관계를 자동으로 주입한다. 즉, @Autowired가 붙은 부분을 자동으로 주입한다.

참고

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

✅ 필드 주입

  • 필드에 바로 주입하는 방법

    특징

    • 코드가 간결해서 많은 개발자들을 유혹하지만 외부에서 변경이 불가능해서 테스트 하기 힘들다는 치명적인 단점이 있다.
    • DI 프레임워크가 없으면 아무것도 할 수 없다.
    • 사용하지 말자!
      • 애플리케이션의 실제 코드와 관계 없는 테스트 코드
      • 스프링 설정을 목적으로 하는 @Configuration 같은 곳에서만 특별한 용도로 사용
@Component
public class OrderServiceImpl implements OrderService {
	@Autowired
	private MemberRepository memberRepository;
	@Autowired
	private DiscountPolicy 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;
	}
}

옵셥처리

  • 주입할 스프링 빈이 없어도 동작해야 할 때가 있다.
  • 그런데 @Autowired만 사용하면 required 옵션의 기본값이 true로 되어 있어서 자동 주입 대상이 없으면 오류가 발생한다.

자동 주입 대상을 옵션으로 처리하는 방법

static class TestBean{
		// 메서드 자체가 호출 안된다
        @Autowired(required = false)
        public void setNoBean1(Member member) {
            System.out.println("member = " + member);
        }
		
        // null 호출
        @Autowired
        public void setNoBean2(@Nullable Member member2) {
            System.out.println("member2 = " + member2);
        }

 		// Optional.empty 호출
        @Autowired
        public void setNoBean3(Optional<Member> member3) {
            System.out.println("member3 = " + member3);
        }
    }
  • 여기서 Member는 스프링 빈이 아니다.
  • setNoBean1()은 ```@Autowired(required=false)이므로 호출 자체가 안된다.

@Nullable, Optional은 스프링 전반에 걸쳐서 지원된다.
예를 들어 생성자 자동 주입에서 특정 필드에만 사용해도 된다.

생성자 주입을 선택해라!

과거에는 수정자 주입과 필드 주입을 많이 사용했지만, 최근에는 대부분이 생성자 주입을 권장한다.

그 이유는 무엇일까?

불변

  • 대부분의 의존관계 주입은 한 번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다.
  • 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안 된다. ( 즉, 불변해야 한다. )
  • 누군가 실수로 변경할 수 도 있고, 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방법이 아니다.
  • 생성자 주입은 객체를 생성할 때 딱 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;
    }
    ...
}
  • @Autowired가 프레임워크 안에서 동작할 때는 의존관계가 없으면 오류가 발생하지만,
  • 지금은 프레임워크 없이 순수한 자바 코드로만 단위 테스트를 수행하고 있다.

아래와 같이 테스트를 실행하면 실행은 된다.

@Test
void createOrder(){
	OrderServiceImpl orderService = new OrderServiceImpl();
    orderService.createOrder(1L, "itemA", 100000);
}
  • 결과는 NFE(Null Point Exception)이 발생하는데,
    그 이유는 memberRepository, discountPolicy 모두 의존관계 주입이 누락되었기 때문이다.

생성자 주입을 사용하면?

주입 데이터가 주락 되었을 때 컴파일 오류가 발생한다.
그리고 IDE에서 어떤 값을 필수로 주입해야 하는지 알 수 있다.

final 키워드

  • 오직 생성자 주입 방식만 final 키워드를 사용할 수 있다.
  • 즉, 생성자 주입을 사용하여 필드에 final 키워드를 사용할 수 있다.
  • 그래서 생성자에서 혹시라도 값이 설정되지 않은 오류를 컴파일 시점에 막아준다.
@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;
	}
	//...
}
  • 여기서 필수 필드인 discountPolicy가 누락되었다.
  • 컴파일 시점에 다음 오류를 발생시킨다.
java: variable discountPolicy might not have been initialized

🧙🏻‍♀️ 컴파일 오류는 세상에서 가장 빠르고, 좋은 오류다!

정리 - 생성자 주입

  • 프레임 워크에 의존하지 않고, 순수한 자바 언어의 특징을 잘 살리는 방법이다.
  • 기본으로 생성자 주입을 사용하고, 필수 값이 아닌 경우에는 수정자 주입 방식을 옵션으로 부여하면 된다. (생성자 주입, 수정자 주입 동시 사용 가능)
  • 항상 생성자 주입을 선택해라! 그리고 가끔 옵션이 필요하면 수정자 주입을 선택해라! 필드 주입은 사용하지 않는게 좋다..!

롬복과 최신 트렌드

  • 대부분이 다 불변이고, 그래서 다음과 같이 필드에 fianl 키워드를 사용하게 된다.
  • 그런데 생성자도 만들어야 하고.. 주입 받은 값을 대입하는 코드도 만들어야 하고..
  • 필드 주입처럼 좀 편리하게 사용하는 방법은 없을까?
    @RequiredArgsConstructor 기능을 사용하면 final이 붙은 필드를 모아서 생성자를 자동으로 만들어 준다.
@Component
@RequiredArgsConstructor // final이 붙은 필드 값을 가지는 생성자 코드를 그대로 만들어줌
public class OrderServiceImp implements OrderService{

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

final 키워드로 선언된 필드를 가지고 있는 생성자를 자동 생성해준다.

생성자 1개를 두고 @Autowired를 생략하는 방법을 사용하고, 거기에 @RequiredArgsConstructor를 함께 사용하여 코드를 더 간결하게 짤 수 있다.

0개의 댓글