Spring/스프링 핵심원리 기본편

[Spring] 의존관계 자동 주입 - 생성자 주입을 선택하자!

딸기케잌🍓 2021. 8. 30. 23:30

의존관계 

의존관계 주입에는 크게 4가지가 있습니다.

  • 생성자 주입
  • 수정자 주입
  • 필드 주입
  • 일반 메서드 주입

 

 

생성자 주입

생성자 호출 시점에 1번만 호출되어 불변, 필수 의존관계에 사용됩니다.

 

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

생성자가 1개일 경우에는 @Autowired를 생략해도 자동 주입됩니다.

 

생성자 주입의 장점

객체를 생성할 때 1번만 호출되므로 의존관계가 한번 일어나면 앱 종료시까지 의존관계가 변하지 않습니다.

수정자 주입 방법은 setXXX 메서드를 public으로 해야하는데 이는 누군가 실수로 변경할 수도 있기에 좋은 설계방법이 아닙니다. 

 

 

수정자(Setter) 주입

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

 

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

 

필드 주입

필드에 바로 주입 하는 방법으로 DI 컨테이너가 없으면 소용없는 방식입니다.

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

앱의 로직과 관계 없는 테스트 코드나 스프링 설정을 목적으로 하는 @Configuration 같은 곳에서 특별한 용도로만 사용하길 추천합니다.

 

<예제3 - 필드 주입>

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

 

 

 

옵션 처리

주입할 스프링 빈이 없을때 의존관계 주입을 옵션으로 처리하는 방법입니다.

@Autowired(required=false) -> 주입대상이 없을 경우 메서드 자체가 호출안됩니다.

org.springframework.lang.@Nullable -> 주입대상이 없을 경우 null이 입력됩니다.

Optional<> : 주입 대상이 없으면 Optional.empty가 입력됩니다.

 

 

아래 예제에서  Member는 스프링 빈이 아닙니다.

setNoBean1에서 @Autowired(required=false)이므로 호출이 아예 안됩니다!

 

<예제5 - 스프링 빈 옵션 처리>

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

 

 

 

생성자 주입을 선택하자

의존관계가 불변

객체 생성시 최초 한번만 호출되므로 의존관계가 불변한다는 것을 보장할 수 있습니다.

수정자 주입의 경우 setXxx 메서드를 public으로 열어두어야 합니다. -> 의존관계가 변할 가능성이 있으므로 좋은 설계가 아닙니다.

 

순수한 자바 언어

프레임워크에 의존하지 않고 순수한 자바 언어를 살릴 수 있습니다.

 

<예제6>

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가 프레임워크 안에서 동작시에는 오류가 발생합니다.

 

 

<예제7>

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

위와 같은 테스트코드는 실행은 되지만 의존관계가 누락이 되었을 경우 컴파일 오류인 Null Pointer Exception이 발생합니다.

 

final키워드를 사용하여 안전한 설계가 가능

생성자 주입을 사용하면 필드에 final 키워드를 사용하여 필드에 값이 설정되지 않았을 경우 컴파일 오류를 내어 알려줍니다.

 

<예제8 - 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;
        }
//...
}

위 예제에서 discountPolicy에 대한 값이 누락되었습니다.

따라서 컴파일 시점에 java : variable discountPolicy might not have been initialized 오류가 발생합니다.

생성자 주입 이외의 모든 주입 방식은 생성자 이후에 호출되므로 필드에 final 키워드를 사용할 수 없고 생성자 주입 방식만 final키워드를 사용할 수 있습니다.

 

결론!!★

기본적으로 생성자 주입을 사용하고 필수 값이 아닌 경우 수정자 주입 방식으로 하는것이 좋고

필드 주입은 최대한 사용하지 않는것이 좋습니다.

 

 

 

롬복 사용

<예제8> 코드를 롬복 라이브러리를 적용하면 더욱 간단하게 줄일 수 있습니다.

@RequiredArgsConstructor : final이 붙은 필드를 이용하여 생성자를 자동으로 만들어줍니다.

 

<예제9 - 롬복을 사용하여 간결해진 코드>

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

 

<예제8>와 <예제9>는 완전히 동일한 코드입니다.

롬복이 자바의 애노테이션 프로세서라는 기능을 이용하여 컴파일 시점에 생성자 코드를 자동으로 생성해줍니다.

실제 class를 보면 아래와 같은 코드가 추가되어 있습니다.

public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy)
{
      this.memberRepository = memberRepository;
      this.discountPolicy = discountPolicy;
}

최근에는 생성자를 1개두고 @Autowired를 생략하는 방법을 주로 사용합니다.

롬복 라이브러리의 @RequiredArgsConstructor를 함께 사용하면 기능은 동일하면서 더 간결한 코드를 작성할 수 있습니다.

 

그렇다면 어떻게 롬복 라이브러리를 적용하는지 알아보겠습니다.

 

롬복 라이브러리 적용방법

커맨드 +, 또는 가장 상단의 Preference 메뉴에 들어가서 아래 그림처럼 lombok을 다운로드 받습니다.

 

 

다운 후 'annotatoin process' 검색 후 Enable annotation processing에 체크해줍니다.

 

마지막으로 build.gradle 파일을 수정합니다.

configurations {
    compileOnly {
        extendsFrom annotationProcessor
	} 
}

 

dependencies에 라이브러리를 추가해줍니다.

dependencies {
 
//lombok 라이브러리 추가 
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok' 

}

 

build.gradle 수정 후 refresh를 잊지말아주세요!

 

External Libraries폴더 하위에 lombok 라이브러리가 추가된 것을 확인할 수 있습니다.