-
[Spring Boot] 생성자 주입이란? + @Autowired 옵션 처리Back-End/Spring Boot 2022. 10. 4. 00:20
의존관계 주입은 크게 생성자, 수정자(setter), 드, 일반 메서드 주입과 같이 4가지가 있는데 생성자 주입이 가장 이상적인 방법이다. 생성자 주입을 사용하자.!
📑 생성자 주입
생성자 주입이란 이름 그대로 생성자를 통해 의존 관계를 주입 받는 방법이다. 생성자 호출 시점에 딱 1번만 호출되는 것이 보장되며 불변, 필수 의존 관계에 사용한다. 생성자가 1개만 있다면 @Autowired를 생략해도 자동 주입이 된다. 물론 스프링 빈에만 해당한다.
@Component public class OrderServiceImpl implements OrderService{ private final MemberRepository memberRepository; //final이 붙어 있음 값을 세팅해줘야한다.! private final DiscountPolicy discountPolicy; //인터페이스에만 의존함. BUT) NullPointException 발생 //@Autowired : 스프링 컨테이너에서 스프링 빈을 꺼내 등록해준다. //불변 데이터, 그 누구도 값을 변경할 수 없다. //생성자가 딱 1개만 있으면 @Autowired를 생략해도 자동 주입 된다. @Autowired public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) { this.memberRepository = memberRepository; this.discountPolicy = discountPolicy; }
과거에는 수정자 주입과 필드 주입을 많이 사용했지만, 최근 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입을 권장한다. 대부분의 의존관계 주입은 한 번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다. 오히려 대부분의 의존간계는 애플리케이션 종료 전까지 변하면 안된다. ( 불변 ! )
📌 생성자 주입을 사용하면 final 키워드를 사용할 수 있다.
혹시라도 생성자에서 값이 설정되지 않는 오류를 컴파일 시점에서 막아준다. 수정자 주입을 포함한 나머지 주입 방식은 모두 생성자 이후에 호출 되므로, 필드에 final 키워드를 사용할 수 없다. 오직 생성자 주입 방식만 final 키워드를 사용할 수 있는 것!
📑 @Autowired의 옵션 처리
@Autowired는 의존관계를 자동으로 주입해준다. 즉) 생성자에 @Autowired 를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다. 타입이 같은 빈을 찾아서 등록한다.
종종, 주입할 스프링 빈이 없어도 동작해야 할 때가 있다. 그런데 @Autowried만 사용하면 required의 기본값이 true로 되어 있어 자동 주입 대상이 없으면 오류가 발생한다.
expected at least 1 bean which qualifies as autowire candidate
자동 주입 대상을 옵션으로 처리하는 방법은 다음과 같다.
1. @Autowired(required=false) : 자동 주입 대상이 없으면 수정자 메서드 자체가 호출 안됨.
2. org.springframework.lang.@Nullable : 자동 주입할 대상이 없으면 null이 입력된다.
3. Optional < >:자동 주입할 대상이 없으면 Optional.empty가 입력된다.package hello.core.autowired; import hello.core.member.Member; import hello.core.member.MemberService; import hello.core.member.MemberServiceImpl; import org.assertj.core.api.Assertions; 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{ private Member bean; @Autowired(required = false) // UnsatisfiedDependencyException 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); } } }
setNotBean1()은 아예 메서드 호출 자체가 안된다. !!
📑 롬복 라이브러리를 이용한 @RequiredArgsConstructor
롬북 라이브러라기 제공하는 @RequiredArgsConstructor 기능을 사용하면 final이 붙은 필드를 모아서 생성자를 자동으로 만들어 준다. 최근에는 생성자를 딱 1개 두고, @Autowired를 생략하는 방법을 선호하기에. Lombok 라이브러리의 기능을 함께 사용하자.
@Component @RequiredArgsConstructor public class OrderServiceImpl implements OrderService { private final MemberRepository memberRepository; private final DiscountPolicy discountPolicy; }
build.gradle에 다음과 같이 Lombok 설정을 추가해주면 사용 가능하다. 물론 롬북 라이브러리가 설치되어 있어야 되는데 설치 방법은 아래 포스팅 참고!
//lombok 설정 추가 시작 configurations { compileOnly { extendsFrom annotationProcessor } } //lombok 설정 추가 끝 dependencies { implementation 'org.springframework.boot:spring-boot-starter' //lombok 라이브러리 추가 시작 compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testCompileOnly 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok' //lombok 라이브러리 추가 끝 } }
https://wonisdaily.tistory.com/2
그 다음 settings -> annotation processors 검색 -> enable annotation proessing 체크 꼭 필수이다.!!
📑 조회 시 빈이 2개 이상
@Autowired는 타입(Type)으로 조회한다. 만약 private DiscountPolicy discountPolicy가 있다면 DiscountPolicy 타입으로 조회하기 때문에 마치, 다음 코드와 유사하게 동작한다. ac.getBean(DiscountPolicy.class)
DiscountPolicy의 하위 타입으로 fix, rate 2가지가 있다고 가정했을 때 여기서 문제가 발생한다.
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name
'orderServiceImpl' defined in file [C:\Users\thdnj\spring-study\core\core\out\production\classes\hello\core\order\OrderServiceImpl.class]: Unsatisfied dependency expressed through constructor parameter 1; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'hello.core.discount.DiscountPolicy' available: expected single matching bean but found 2: fixDiscountPolicy,rateDiscountPolicy메시지를 살펴보면 하나의 빈을 기대했는데 2개가 발견되었다고 알려준다. 이때 하위 타입을 직접 지정할 수 있지만, 하위 타입으로 지정하는 것은 DIP를 위배하고 유연성이 떨어진다.
이럴 때 해결할 수 있는 방법이 3가지 있다.
1. @Autowired 필드 명 매칭
2. @Qualifier 빈 이름 매칭
3. @Primary 사용📌 1. @Autowired 필드 명 매칭
@Autowired는 타입 매칭을 시도하고, 이때 여러 빈이 있으면 필드 이름, 파라미터 이름으로 빈 이름을 추가 매칭한다. 예를들어 @Autowired private DiscountPolicy discountPolicy 이렇게 선언된 기존 코드의 필드명을 빈 이름으로 변경한다.
@Autowired
private DiscountPolicy rateDiscountPolicy이렇게 되면 필드 명으로 정상 주입 된다. !
📌 2. @Qualifier 빈 이름 매칭
@Qualifier는 추가 구분자를 붙여주는 방법이다. 주입시 추가적인 방법을 제공하는 것이지 빈 이름을 변경하는 것은 아니다. 아래와 같이 @Qualifiler("")에 이름을 적어준다.
생성자 자동 주입할 땐 @Qualifier로 지정해둔 이름으로 빈을 찾아 반환한다.
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) { this.memberRepository = memberRepository; this.discountPolicy = discountPolicy; }
📌 3. @Primary 사용
@primary는 우선 순위를 정하는 방법이다. @Autowired 시에 여러 번 매칭 되면 @Primary가 우선권을 가진다. 만약 rateDiscountPolicy가 우선권을 가지게 하려면 다음과 같다.
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy { }이렇게 설정만 해두고 맨 처음 코드처럼 동작시켜 보면 문제 없이 작동하는 걸 확인할 수 있다. 그럼 @Primary와 @Qualifier 중 어떤 걸 사용하면 좋은거야? 라는 의문이 생길 수 있다.
코드에서 자주 사용하는 메인 데이터베이스의 커넥션을 획득하는 스프링 빈이 있고, 코드에서 특별한 기능으로 가끔 사용하는 서브 데이터베이스이 커넥션을 획득하는 스프링 빈이 있다고 생각해본다.
메인 데이터베이스의 커넥션을 획득하는 스프링 빈은 @Primary 를 적용해서 조회하는 곳에서 @Qualifier 지정 없이 편리하게 조회하고, 서브 데이터베이스 커넥션 빈을 획득할 때는 @Qualifier 를 지정해서 명시적으로 획득 하는 방식으로 사용하면 코드를 깔끔하게 유지할 수 있다
@Primary는 기본값 처럼 동작하는 것이고, @Qualifier는 매우 상세하게 동작하기 때문에 @Qualifier가 우선권이 높다.
반응형'Back-End > Spring Boot' 카테고리의 다른 글
[Spring Boot] Bean 생명주기 (Life cycle) 콜백 (0) 2022.10.11 [Spring Boot] 빈 스코프 범위 (싱글톤, 프로토타입, 웹) (0) 2022.10.06 [Spring Boot] 컴포넌트 스캔과 의존관계 자동 주입 (1) 2022.10.03 [Spring Boot] 싱글톤 패턴 vs 싱글톤 컨테이너 ( @Configuration) (1) 2022.10.01 [Spring Boot] 스프링 컨테이너 ApplicationContext 생성과 빈 등록, 의존 관계 설정 (0) 2022.09.30