Blog

[SpringCore] 의존관계 자동 주입 방법들과 생성자 주입을 권장하는 이유

Category
Author
Tags
PinOnMain
1 more property
의존관계 주입 방법
Table of Content

의존 관계 주입 방법

생성자 주입
수정자(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; } ...
Java
복사
다음 처럼 생성자(Constructor)를 통해서 의존 관계를 주입 받는 방법이다.
생성자 호출 시점에 딱 한번 호출되는 것이 보장
“불변”, “필수” 의존 관계에 사용
생성자가 딱 1개 있으면 위 @Autowired를 생략해도 자동 주입 된다.

2. 수정자 주입(setter)

@Component public class OrderServiceImpl implements OrderService{ private MemberRepository memberRepository; private DiscountPolicy discountPolicy; //수정자 주입(setter) @Autowired public void setDiscountPolicy(DiscountPolicy discountPolicy) { this.discountPolicy = discountPolicy; } @Autowired public void setMemberRepository(MemberRepository memberRepository) { this.memberRepository = memberRepository; }
Java
복사
필드의 값을 변경하는 수정자 메서드를 통해서 의존관계를 주입하는 방법
“선택”, “변경” 가능성이 있는 의존관계에 사용
@Autowired 의 기본 동작은 주입 대상이 없으면 발생한다
@Autowired(required = false)를 지정하면 된다.

3. 필드 주입

@Component public class OrderServiceImpl implements OrderService{ @Autowired private MemberRepository memberRepository; @Autowired private DiscountPolicy discountPolicy; ...
Java
복사
필드에 바로 @Autowired 로 주입하는 방식이다.
코드가 간결해 보이지만 외부에서 변경이 불가능해서 테스트하기 어려운 치명적인 단점
DI 프레임워크가 없으면 아무것도 할 수 없다
사용 X
애플리케이션의 실제 코드와 관계없는 테스트코드
스프링 설정 목적으로하는 @Configuration 같은 곳에서만 특별한 용도로 사용

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; } ...
Java
복사
일반 메서드를 통해 주입 받을수도 있다.
한번에 여러 필드를 주입 받을 수 있다.
일반적으로 잘 사용되지 않음
보통 생성자 주입으로 해결되기 때문에 잘 사용되지 않음

옵션 처리

주입할 스프링 빈이 없어도 동작할 때가 있다. 그런데 @Autowired 만 사용하면 required 옵션이 기본값 ‘true’로 되어 있어 자동 주입 대상이 없으면 오류가 발생한다.
자동 주입 대상을 옵션으로 처리하는 방법은 다음과 같다.
package hello.core.autowired; import hello.core.member.Member; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.lang.Nullable; import java.util.Optional; public class AutowiredTest { @Test void autowiredOption() { ApplicationContext ac = new AnnotationConfigApplicationContext(TestBean.class); } static class TestBean { @Autowired(required = false) public void setNoBean1(Member noBean1) { System.out.println("noBean1 = " + noBean1); } @Autowired public void setNoBean2(@Nullable Member noBean2) { System.out.println("noBean2 = " + noBean2); } @Autowired public void setNoBean3(Optional<Member> noBean3) { System.out.println("noBean3 = " + noBean3); } } }
Java
복사
스프링 빈이 아닌(빈 등록을 명시 하지 않은 클래스)인 Member 객체를 넣었다.
@Autowired(required = false) : 자동주입할 대상이 없으면 수정자 메서드 자체가 호출이 안된다.
@Nullable : 자동 주입할 대상이 없으면 null이 입력된다.
Optional<> : 자동 주입할 대상이 없으면 Optional.empty가 입력된다.

생성자 주입이 아닌 수정자 주입 등인 경우

@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; } ...
Java
복사
만약 수정자 주입으로 OrderServiceImpl 이 주입되는 상황이면 이와 같이 NullPointerException이 발생한다.
createOrder
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);
처럼 repositorydiscount 의존관계가 들어가있는 로직이기 때문 이 부분이 없다고 나타나는 오류이다.
package hello.core.order; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class OrderServiceImplTest { @Test void createOrder() { OrderServiceImpl orderService = new OrderServiceImpl(); orderService.createOrder(1L, "itemA", 10000); } }
Java
복사

생성자 주입을 권장하는 이유

1. 불변

대부분 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다
대부분 의존관계는 변하면안된다.
수정자 주입을 사용하면 setter 메서드를 public으로 열어두므로 수정에 열린 설계 방법은 좋지 않다.
생성자 주입은 객체 생성시 딱 한번만 호출되므로 불변하게 설계 할 수 있다.

2. 누락방지

프레임워크 없이 순수한 자바 코드 단위테스트 하는 케이스가 많다. 이 때 생성자 주입을 통해서는 누락된 파라미터를 빠르게 확인(컴파일 시점)할 수 있다.
package hello.core.order; import hello.core.discount.FixDiscountPolicy; import hello.core.member.Grade; import hello.core.member.Member; import hello.core.member.MemoryMemberRepository; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; class OrderServiceImplTest { @Test void createOrder() { MemoryMemberRepository memberRepository = new MemoryMemberRepository(); memberRepository.save(new Member(1L, "name", Grade.VIP)); OrderServiceImpl orderService = new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy()); Order order = orderService.createOrder(1L, "itemA", 10000); assertThat(order.getDiscountPrice()).isEqualTo(1000); } // 생성자 주입으로는 OrderServiceImpl을 생성할때부터 필요한 파라미터를 명시해야 하도록한다.(필수) // 생성자 주입 방식만이 필드에 초기화에 final(불변)을 넣을 수 있기 때문에 좋은 설계가 가능 }
Java
복사
Search
 | Main Page | Category |  Tags | About Me | Contact | Portfolio