의존관계 자동주입 1편

MinHo·2024년 4월 10일

Spring

목록 보기
1/12
post-thumbnail

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

이 4가지에 대해서 알아보고 어떤 방식을 선택해야 좋을지 알아볼 예정이다.

1. 생성자 주입

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

생성자 주입의 특징

  • 생성자 호출시점에 딱 1번만 호출되는 것이 보장된다. (final)
  • 불변, 필수 의존관계에 주로 사용
@Component
public class OrderServiceImpl implements OrderService {
 	
    private final MemberRepository memberRepository;
 	private final DiscountPolicy discountPolicy;
 
 	@Autowired //중요! 생성자가 딱 1개만 있으면 @Autowired를 생략해도 자동 주입 된다.
 	public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
 			this.memberRepository = memberRepository;
 			this.discountPolicy = discountPolicy;
 	}
}

2. 수정자 주입(setter 주입)

필드의 값을 변경하는 수정자 메서드 (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;
 	}
}

3. 필드 주입

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

  • 외부에서 변경이 불가능해서 테스트하기 힘들다는 치명적인 단점이 있다.
  • DI 프레임워크가 없으면 아무것도 할 수 없다. ( 순수 java Test 불가 )
  • 사용하지말자!!
@Component
public class OrderServiceImpl implements OrderService {
 	
    @Autowired
 	private MemberRepository memberRepository;
    @Autowired
 	private DiscountPolicy discountPolicy;
}

4. 일반 메서드 주입

  • 한번에 여러 필드를 주입 받을 수 있다.
  • 일반적으로 잘 사용하지 않는다.
@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가지의 주입 방법이 있다. 이제는 옵션처리에 대해 알아보자.


옵션 처리

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

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

  • @Autowired(required=false) : 자동 주입할 대상이 없으면 메서드 자체가 호출 안됨.
  • org.springframework.lang.@Nullable : 자동 주입할 대상이 없으면 null이 입력된다.
  • Optional<> : 자동 주입할 대상이 없으면 Optional.empty 가 입력된다.

코드로 확인해보자.

// Member는 스프링 빈이 아니다.
// setNoBean1() 은 @Autowired(required=false) 이므로 호출 자체가 안된다.

// 호출 안됨
@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);
}

// 출력결과 
setNoBean2 = null
setNoBean3 = Optional.empty

옵션까지 알아봤고 그럼 과연 어떤 주입 방식을 선택해야하는걸까?


생성자 주입을 선택해라!!

과거에는 수정자, 필드 주입을 많이 사용했지만 최근에는 DI프레임워크 대부분이 생성자 주입을 권장한다. 이유는 다음과 같다.

불변

  • 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안된다. (불변해야 한다!)
  • 수정자 주입을 사용하면, set메서드를 public으로 열어두어야한다.
  • 누군가 실수로 변경할 수 도 있고, 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방법이 아니다.
  • 생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에 호출되는 일이 없다. 따라서 불변하게 설계할 수 있다.

Test 코드를 통해서 생성자 주입이 좋은 이유를 알아보도록 하자!
프레임워크 없이 순수한 자바코드를 단위 테스트 하는 경우를 통해서 비교해보겠다.

수정자 의존관계인 경우

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 코드

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

수행하면 실행은 된다. 하지만!!! NPE(NullPointException)이 발생하게 된다. 의존관계 주입이 누락되었기 때문이다.
같은 Test를 생성자 주입을 통해 수행했을때는 컴파일 오류가 발생한다. 또한 생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있다. 그래서 생성자에서 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에 막아준다.

김영한 쌤 " 기억하자! 컴파일 오류는 세상에서 가장 빠르고, 좋은 오류다! "

지금까지의 정보를 정리해보자.

  • 생성자 주입 방식은 순수하게 자바 Test를 할 때 자바 언어의 특징을 잘 살리는 방식이다.
  • 생성자 주입을 선택해라! 그리고 아주 가끔 옵션이 필요하면 수정자 주입을 선택해라. 필드 주입은 사용하지 않는게 좋다.

이제 생성자 주입방식 4가지, 옵션처리, 어떤 주입방식이 좋을까? 까지 알아봤다.

우리 개발자들은 코드를 한줄이라도 줄이고 편하고 가독성있게 쓰기위해 노력한다.
롬복과 최신트렌드를 통해서 최근에 어떻게 사용하는지 알아보자


롬복과 최신 트렌드

개발을 해보면 대부분이 다 불변이다. 그래서 필드에 final을 사용하게 된다.
생성자, 주입받은 값 대입 등등 귀찮은것이 많다. 최적화 해보자!

//AS-IS
@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;
 	}
}


//TO-BE V1
@Component
public class OrderServiceImpl implements OrderService {
 	private final MemberRepository memberRepository;
 	private final DiscountPolicy discountPolicy;
    
 	public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
 		this.memberRepository = memberRepository;
 		this.discountPolicy = discountPolicy;
 	}
} //생성자 하나이기에 @Autowired는 생략가능하다.


//TO-BE V2 롬복 적용!!
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
 	private final MemberRepository memberRepository;
 	private final DiscountPolicy discountPolicy;
}
//롬복 라이브러리가 제공하는 @RequiredArgsConstructor 기능을 사용하면 final이 붙은 필드를 모아서 생성자를 자동으로 만들어준다. 

최근에는 생성자를 딱 1개두고, @Autowired를 생략하는 방법을 주로 사용한다. 여기에 Lombok 라이브러리의 @RequiredArgsConstructor를 사용하면 기능은 다 제공하면서, 코드는 깔끔하게 사용할 수 있다.

길어지는 관계로 의존관계 2편에서 이어서....

profile
Keep going

0개의 댓글