[spring 핵심 기본] 의존관계 자동 주입

채원·2024년 7월 21일

스프링

목록 보기
15/18
post-thumbnail

출처) 인프런 스프링 핵심 원리 기본편 강의

다양한 의존관계 주입

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

생성자 주입

생성자를 통해 의존 관계를 주입 받는 방법

  • 생성자 호출 시점에 딱 한 번 호출되는 것이 보장 (외부에서 수정 불가)
  • 주로 불변, 필수 의존 관계에 사용
    ⭐️ 스프링 빈의 생성자가 딱 1개만 있으면 생성자에 @Autowired를 생략해도 자동 주입

수정자 주입 (setter)

필드 값을 변경하는 수정자 메서드 setter를 통해 의존관계를 주입하는 방법

  • 선택, 변경이 있는 의존 관계에 사용
  • 기존 생성자 코드를 지우고 수정자만 남겨도 됨 (생성자에서 주입한 빈을 다 setter 메서드 만들기)

setter 생성

  • set필드명 으로 네이밍해서 setter 생성
  • @Autowired 붙이기
@Autowired
public void setMemberRepository(MemberRepository memberRepository){
	this.memberRepository = memberRepository;
    }

⭐️ 만약 필드가 필수가 아니면, @Autowired(required = false)로 주면 됨
주입할 대상이 없으면, 오류가 발생하니 주의

참고) 자바빈 프로퍼티

자바는 과거부터 필드 값을 직접 변경하지 않고, set00, get00라는 메서드를 통해 값을 수정하고, 읽는 규칙이 있음

필드 주입

필드에 바로 주입하는 방법

@Autowired private MemberRepository memberRepository; 
  • 코드가 간결하지만, 외부에서 변경이 불가해서 테스트 어려움
  • DI 프레임 워크가 없으면, 아무것도 할 수 없음
  • 애플리케이션 코드에서 사용 ❌
    - 애플리케이션 실제 코드와 관계없는 테스트 코드에서 사용 가능
    • @Configuration 파일에서 사용 가능

일반 메서드 주입

아무 메서드에 @Autowired 사용

  • 일반적으로 잘 사용하지 않음
  • 한 번에 여러 필드 주입 가능

옵션 처리

주입할 스프링빈이 없어도 (optional) 동작해야할 때
@Autowired의 주입 대상이 없으면, 무조건 오류가 발생

  • @Autowired(required=false): 자동 주입 대상이 없으면 setter 호출 불가
    @Autowired(required = false)
  • org.springframework.lnag.@Nullable: 자동 주입 대상이 없으면 null 입력
public void setNoBean2(@Nullable Member noBean2) {
  • Optional<> 자동 주입할 대상이 없으면 Optional.empty 입력
public void setNoBean3(Optional<Member> noBean3) {

결론: 생성자 주입을 선택하자

여러 의존 관계 주입 방식이 있지만, 그 중 생성자 주입을 선택!!

  • 대부분 의존관계 주입은 불변해야함
    - 생성자 주입만 final 키워드 사용 가능
  • 수정자 주입을 사용하면 세터를 public으로 두어야하는데, 좋은 설계가 아니다
  • 생성자 주입 사용시 데이터 누락이 발생하면 컴파일 오류로 확인이 쉽다

롬복 (Lombok)

생성자 주입을 편하게 해주는 라이브러리
대부분 불변이므로 final 키워드를 사용함

프로젝트 생성할 때 롬복을 체크하고 할 수도 있음, build.gradle에 추가하기
설정 > 컴파일러 > 어노테이션 프로세서 > 활성화 클릭

@Getter, @Setter, @ToString
어노테이션만 추가하면, 해당 메서드를 자동으로 만들어줌

⭐️ @RequiredArgsConstructor: final 키워드가 붙은 필드의 생성자를 자동으로 만들어줌
컴파일 시점에 롬복이 생성자 코드를 생성

@Component
@RequiredArgsConstructor
 public class OrderServiceImpl implements OrderService {
     private final MemberRepository memberRepository;
     private final DiscountPolicy discountPolicy;
     
     // 생성자 없어도 됨
 }

최근 스타일
생성자 1개 (Autowired 생략) + @RequiredArgsConstructor 조합으로 사용

조회된 빈이 2개 이상인 문제

@Autowired는 타입으로 조회함
같은 타입인 빈이 2개 이상일 때는 NoUniqueBeanDefinitionException 발생
하위 타입을 지정하면 DIP를 위반함.

해결 방법

1. @Autowired 필드명 매칭
@Autowired 매칭

    1. 타입 매칭을
    1. 여러 개가 있는 경우 필드 이름 or 파라미터 이름으로 추가 매칭
@Autowired
private DiscountPolicy discountPolicy

//===== 변경한 코드
private DiscountPolicy rateDiscountPolicy

2. @Qulifier 사용
추가 구분자, 빈 이름 변경하는 것 X
@Qualifier는 @Qualifier를 찾는 용도로만 사용하는 것이 좋다

@Component
@Qualifier("mainDiscountPolicy")
private DiscountPolicy discountPolicy

3. @Primary 사용
우선 순위 지정, @Autowired로 여러 빈이 매칭되면 @Primary가 우선권 가짐
메인 DB를 주로 Primary로 둠

@Component
@Primary
private DiscountPolicy discountPolicy

우선 순위
@Primary vs @Qualifier
Qualifier가 Primary보다 상세하게 동작하고, 우선권이 있음

애노테이션 직접 만들기

@Qualifier("mainDiscountPolicy") 이런식으로 문자를 적는 경우 컴파일 시
타입 체크가 안 됨 → 애노테이션을 직접 만들어 해결
core/annotation 패키지 생성
아래 annotation 타입으로 체크하고 MainDiscountPolicy 생성

package hello.core.annotation;

import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.annotation.*;

@Target({
  ElementType.FIELD,
  ElementType.METHOD,
  ElementType.PARAMETER,
  ElementType.TYPE,
  ElementType.ANNOTATION_TYPE
})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {}

이렇게 생성해주고, main으로 사용할 할인 정책에서는
Qualifier("mainDiscountPolicy") 이렇게 쓰지 않고, 만든 애노테이션
@MainDiscountPolicy를 적어줌

@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {
...

대부분은 스프링이 제공하는 애노테이션에서 해결이 됨.
뚜렷한 목적 없이 무분별한 재정의는 유지보수에 혼란을 가중할 수 있으니 주의

조회한 빈이 모두 필요할 때 List, Map

Map<String, DiscountPolicy>
key = 빈이름, value = 값으로 DiscountPolicy 타입으로 조회한 모든 스프링 빈 담아줌

List<DiscountPolicy> :
DiscountPolicy 타입으로 조회한 모든 스프링 빈을 담아줌.
만약 해당하는 타입의 스프링 빈이 없으면, 빈 컬렉션이나 Map을 주입

자동, 수동 올바른 실무 운영 기준

결론) 자동 스캔을 선호하는 추세, 자동 빈 등록을 해도 OCP, DIP를 지킬 수 있음
업무 로직 빈은 자동 등록, 기술 지원 빈은 수동 등록을 사용하는 것이 좋음.

업무 로직, 기술 지원빈

어플리케이션은 업무와 기술지원 로직으로 나눌 수 있음

  • 업무 로직 빈: 웹 지원 컨트롤러, 핵심 비즈니스 로직이 있는 서비스, DB 계층 로직 처리 레포지 등 비즈니스 요구사항에 따라 추가되거나 변경 됨. 수가 많고, 유사 패턴이 있음
    → 자동 등록 사용
  • 기술 지원 빈: 기술적 문제, AOP 처리시 주로 사용, 수가 적고 어플리케이션에 광범위 한 영향을 줌
    → 수동 등록 사용
    명확하게 들어내는 것이 좋음

추가로 비즈니스 로직에서 다형성을 적극 활용할 때
다른 개발자가 코드를 파악하기 위해서 여러 코드를 열어봐야하므로, 수동 등록을 하거나, 자동 등록시 같은 패키지에 묶어두는 것이 좋다.
예를 들어)
DiscountService의 경우 DiscountPolicy가 여러 개이기 때문에,
한 눈에 알아볼 수 있도록 별도의 설정 정보를 생성하여 수동 등록하는 것이 좋다.

@Configuration
public class DiscountPolicyConfig{
	@Bean
    public DiscountPolicy rateDiscountPolicy(){
    	return new RateDiscountPolicy();
    }
    @Bean
    public DiscountPolicy fixDiscountPolicy(){
    	return new FixDiscountPolicy();
    }
}

0개의 댓글