DI(의존성 주입) 방식별 장단점

Hansu Kim·2022년 2월 3일
0

Spring boot

목록 보기
3/10

Spring framework에서는 Dependency Injection으로 3가지 방식을 지원하고 있다.

  • 생성자 주입
  • 수정자(Setter) 주입
  • Field 주입

본 포스트에서는 각 방식별 장단점에 대해서 서술한다.

Field 주입

@Controller
public class MemberController {
   @Autowired
   private MemberRepository memberRepository
}

장점

  • 코드가 간결해진다.

단점

  • 외부에서 변경이 불가능해진다. (필드의 객체 수정이 불가능하다.)
    특히, 테스트 코드 작성시 객체 수정이 불가능하므로 어려움을 겪게 됨.
    (스프링 프레임워크에서는 빈이 싱글톤으로 생성되기에 테스트 코드 작성이 불가능 한 것은 아니나, DI 프레임워크에 의존적인 테스트 코드는 바람직하지 않은 구현이다.)

    필드 주입 방식은 일반적으로 권장되지 않으나, 실무에서는 테스트 코드나 @Configuration 등에서는 편의를 위해 활용된다.

수정자(Setter) 주입

public class UserService {
   private MemberRepository memberRepository;
   private MemberService memberService;
   
   @Autowired
   public void setUserRepository(UserRepository userRepository){
      this.memberRepository = memberRepository
   }
}

만약 주입할 객체가 생성되기 전이라면 오류가 발생하므로, @Autowired(required=false)로 설정해줘야 한다.

장점

  • 객체 생성 이후에도 객체를 변경시킬 수 있다.
  • 선택적으로 생성해야하는 객체나, 변경이 발생하는 의존관계에서 사용할 수 있다.

생성자 주입

생성자 주입은 생성자를 통해 의존 관계를 주입시키며, 생성자가 1개일 경우 @Autowired는 생략 가능하다. (가능하지만 생략하지 말자)

public class MemberService {
   private MemberRepository memberRepository;
   
   @Autowired
   public MemberService(MemberRepository memberRepository){
      this.memberRepository = memberRepository
   }
}

장점

  • 생성자의 호출시점에 1회 호출되는 것이 보장된다.
  • 주입받은 객체가 변하지 않는다.
  • DI를 강제할 수 있다.

생성자 주입 방식을 사용해야 하는 이유

객체의 불변성 확보

스프링 프레임워크에서 Bean 간 Dependency가 변경되어야 하는 경우는 실제로 거의 없다. 그러므로 생성자 주입을 통해 변경 가능성을 배제하고 불변성을 보장하는 것이 좋다.

테스트 코드 작성의 편의성

필드 주입 방식으로 Bean의 DI가 정의되었다면, 순수 자바코드로 단위 테스트를 작성할 수 없다.
테스트 코드는 Spring 프레임워크 상에서 동작하지 않으므로, 각 Bean들이 싱글톤으로 생성되어있음을 보장할 수 없다.
하지만 생성자 주입 방식을 사용한다면, 컴파일 시점에 객체를 주입받으므로 테스트 코드 작성이 가능하며, 컴파일 시점에 주입관계에서의 오류까지 잡아낼 수 있다.

final, Lombok과의 결합

생성자 주입 사용시, 필드 객체에 final을 사용할 수 있다. (필드 주입 방식에서는 DI가 객체 생성 이후에 수행되므로 final 사용 불가).
또한, final을 사용하면 Lombok을 활용할 수 있다.

Lombok: Annotation 기반 코드 자동완성 라이브러리
ex) @Getter, @Setter

@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
  private final UserRepository userRepository;
  private final MemberService memberService; 
  @Override public void register(String name) {
  userRepository.add(name); 
  }
}

@RequiredArgsConstructor는 클래스의 필드들 중 @NonNull이 붙은 필드와 final로 생성된 필드들을 아규먼트로 하는 생성자를 자동으로 관리해주는 어노테이션이다,

@Getter 
@RequiredArgsConstructor 
public class Store extends Common {
@NonNull private String companyName; // 상호명 
private final String industryTypeCode; // 업종코드 
private String businessCodeName; // 업태명 
private String industryName; // 업종명(종목명) 
private String telephone; // 전화번호

순환 참조 에러 방지

Bean들이 필드/Setter 주입 방식으로 서로를 순환참조 하고 있다면, 서로를 순환해서 호출하며 CallStack이 계속 쌓여 Stackoverflow가 발생한다. 반면에 생성자 주입 방식의 경우, 같은 런타임이어도 애플리케이선 구동 시점에 에러가 발생하므로 해당 에러 발생을 사전에 방지할 수 있다.

DI시의 옵션 처리

주입할 스프링 빈이 없어도 동작해야 할 때가 있다.
하지만, @Autowired 사용시 required=true가 디폴트로 처리되기에 DI 대상이 없으면 오류가 발생한다.

그 경우 자동 주입 대상이 없어도 오류가 발생하지 않도록 별도 처리를 해주어야 한다.
해당 방법은 아래 3가지가 있다.

  • @Autowired(required=false): DI 대상이 없으면 수정자 메서드 자체가 호출 안됨.
  • org.springframework.lang.@Nullable: 자동 주입할 대상이 없으면 null이 입력됨.
  • Optional<>: 자동 주입할 대상이 없으면 Optional.empty가 입력됨.
    @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);
        }
        @Autowired
        public void noBeanTest(){
            Member test = new Member(1L,"hans", Grade.VIP);
            setNoBean1(test);
        }
    }
}
//위 코드의 출력값
noBean2 = null
noBean3 = Optional.empty
noBean1 = group.core.member.Member@8c3619e

참조블로그: https://mangkyu.tistory.com/78

0개의 댓글