본문 바로가기
내가 공부하려고 올리는/스프링

의존 관계 주입

by 결딴력 2022. 2. 3.
반응형

 

의존관계 주입

  • 생성자 주입
  • 수정자 주입(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;
    }
}

 

 

 

수정자(setter) 주입

  • setter라 불리는 필드 값을 변경하는 수정자 메서드를 통해 의존 관계를 주입하는 방식
  • 선택, 변경 가능성이 있는 의존관계에 사용
  • 예시
@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 프레임워크 없이는 동작할 수 없다.
  • 예시
@Component
public class OrderServiceImpl implements OrderService {
    
    @Autowired
    private MemberRepository memberRepository;
    
    @Autowired
    private DiscountPolicy discountPolicy;
}

 

 

 

일반 메서드 주입

  • 일반 메서드를 통해 의존 관계를 주입받는다.
  • 한 번에 여러 필드를 주입받을 수 있다.
  • 일반적으로 잘 사용하지 않는다.

 

 

 

옵션 처리

  • @Autowired의 기본 옵션은 required = true이다.
  • required 옵션이 true이면 자동 주입 대상이 없을 때 오류가 발생한다.
  • 자동 주입 대상을 옵션으로 처리하는 방법은 다음과 같다.
    • @Autowired(required = false) : 자동 주입할 대상이 없으면 메서드 자체가 호출되지 않는다.
    • org.springframework.lang.@Nullable : 자동 주입할 대상이 없으면 null이 입력
    • Optional<> : 자동 주입할 대상이 없으면 Optional.empty가 입력
  • 예시
//호출 안됨
@Autowired(required = false)
//Member 클래스는 스프링 빈으로 관리되지 않는다.
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
public void setNoBean3(Optional<Member> member) {
    System.out.println("setNoBean3 = " + member);
}

 

 

생성자 주입

  • 최근에는 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입을 권장
  • 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료 시점까지
    의존 관계가 변경되지 않는 것이 대부분
  • 수정자 주입을 사용하는 경우 set 메서드를 public으로 열어둬야 하기 때문에
    누군가 실수로 값을 변경하는 등의 문제가 발생할 수 있다.

 

 

final 키워드

  • 생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있다.
  • 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;
        this.discountPolicy = discountPolicy;
    }
}

 

  • 생성자가 1개만 있는 경우 @Autowired생략할 수 있다.
  • 따라서 다음과 같이 @Autowired를 생략할 수 있다.
@Component
public class OrderServiceImpl implements OrderService {
    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;
    
    public OrderServiceImpl(MemberRepository memberRepository, 
    			    DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
}

 

  • 이 상태에서 롬복을 적용해보자.
  • 롬복 라이브러리가 제공하는 @RequiredArgsConstructor 기능을 사용하면,
    final이 붙은 필드를 모아서 생성자를 자동으로 만들어준다.
  • 이 기능을 사용하면 코드에는 보이지 않지만 실제로는 호출이 가능하다.

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

 

  • 롬복을 사용하면 위와 같이 코드를 단순화할 수 있다.

 

 

롬복 라이브러리 적용 방법

  • build.gradle에 라이브러리 및 환경 추가
//lombok 설정 추가 시작
configurations {
   compileOnly {
      extendsFrom annotationProcessor
   }
}
//lombok 설정 추가 끝

dependencies {
   //lombok 라이브러리 추가 시작
   compileOnly 'org.projectlombok:lombok'
   annotationProcessor 'org.projectlombok:lombok'
   testCompileOnly 'org.projectlombok:lombok'
   testAnnotationProcessor 'org.projectlombok:lombok'
   //lombok 라이브러리 추가 끝

 

  • (윈도우) File -> Settings -> plugin -> lombok 검색 설치 실행

 

 

조회되는 빈이 2개 이상일 때

  • @Autowired타입으로 조회
  • 마치 .getBean(부모클래스.class)처럼 동작
  • 부모 클래스의 하위 타입이 여러 개일 경우 하위 타입을 모두 스프링 빈으로 등록하고
    의존관계 자동 주입을 실행하면 NoUniqueBeanDefinitionException 오류가 발생
  • 이때 하위 타입으로 의존 관계 주입을 실행하면 오류는 해결되지만,
    하위 타입으로 지정하는 것은 DIP를 위배하고 유연성이 떨어진다.
  • 또한 이름만 다르고, 완전히 똑같은 타입의 스프링 빈이 2개 있을 경우
    하위 타입으로 지정하는 방식은 문제를 해결할 수 없다.

 

 

해결 방법

  • @Autowired 필드 명 매칭
  • @Qualifier -> @Qualifier끼리 매칭 -> 빈 이름 매칭
  • @Primary 사용

 

 

@Autowired 필드 명 매칭

  • @Autowired타입 매칭을 시도하고, 빈이 여러 개면 필드 이름, 파라미터 이름으로
    빈 이름을 추가 매칭 한다.
  • 기존 코드
@Autowired
private DiscountPolicy discountPolicy

 

  • 필드 명을 빈 이름으로 변경
@Autowired
private DiscountPolicy rateDiscountPolicy

 

  • 필드 명을 연결하려고 하는 하위 타입의 이름으로 변경
  • 필드 명 매칭은 먼저 타입 매칭을 시도하고 그 결과에 여러 빈이 있을 때 추가로 동작하는 기능이다.

 

 

@Qualifier 사용

  • @Qualifier추가 구분자를 붙여주는 방식
  • 주입 시 추가적인 방법을 제공하는 것으로 빈 이름을 변경하는 것은 아니다.
  • 예시
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}

 

  • 빈 등록 시 @Qualifier를 붙인다.
  • 주입 시에 @Qualifier를 붙여주고 등록한 이름을 적어준다.
  • 예시
//생성자 자동 주입
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
                        @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}


//수정자 자동 주입
@Autowired
public DiscountPolicy setDiscountPolicy(
				@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
    return discountPolicy;
}

 

  • @Qualifier 사용 시 지정한 @Qualifier의 이름을 찾지 못하면,
    지정한 이름으로 등록된 스프링 빈이 있는지 추가적으로 탐색한다.

 

 

@Primary 사용

  • @Primary우선순위를 정하는 방법이다.
  • @Autowired 시에 여러 빈이 매칭 되면 @Primary우선권을 갖는다.
  • 예시 : rateDiscountPolicy에게 우선권을 부여하기
@Component
@Primary //우선권 부여
public class RateDiscountPolicy implements DiscountPolicy {}

@Component
public class FixDiscountPolicy implements DiscountPolicy {}

 

  • RateDiscountPolicy에 우선권이 부여되어 있다.
  • @Autowired를 사용해 DiscountPolicy에 의존 관계 부여 시 RateDiscountPolicy가 우선권을 갖는다.

 

 

@Primary, @Qualifier 사용

  • @Primary와 @Qualifier가 동시에 사용될 때 @Qualifier의 우선순위가 높다.
  • 메인 데이터베이스의 커넥션을 획득하는 스프링 빈은 @Primary를 사용하는 것이 좋다.
  • 서브 데이터베이스의 커넥션 빈을 획득할 때는 @Qualifier를 사용하는 것이 좋다.

 

 

 

수동 빈 등록 vs 자동 빈 등록

  • 애플리케이션은 크게 업무 로직기술 지원 로직으로 나눌 수 있다.
    • 업무 로직 빈
      웹을 지원하는 컨트롤러, 핵심 비즈니스 로직이 있는 서비스,
      데이터 계층의 로직을 처리하는 리포지토리 등이 모두 업무 로직이다.
      보통 비즈니스 요구사항을 개발할 때 추가되거나 변경된다.
    • 기술 지원 빈
      기술적인 문제나 공통 관심사(AOP)를 처리할 때 주로 사용된다.
      데이터베이스 연결이나, 공통 로그 처리처럼 업무 로직을 지원하기 위한
      하부 기술이나 공통 기술들을 말한다.
  • 업무 로직은 로직의 수도 많고 개발할 때 유사한 패턴을 보이는 경우가 많기 때문에
    자동 빈 등록 기능을 적극적으로 활용한다.
  • 기술 지원 로직은 업무 로직에 비해 그 수가 현저히 적고, 애플리케이션 전반에 걸쳐
    광범위하게 영향을 미친다. 또 업무 로직은 문제가 발생했을 때 어디가 문제인지
    명확하게 잘 드러나지만, 기술 지원 로직은 문제 파악이 비교적 힘들다.
    따라서 이러한 기술 지원 로직들은 가급적 수동 빈 등록을 사용한다.
  • 업무 로직 중에서도 다형성을 적극 활용할 때는 수동 빈 등록을 사용한다.
    예를 들어, 할인 정책이 여러 개이고 그때그때 의존 관계를 다르게 주입해야 할 때,
    자동 빈 등록을 사용하면 어떤 빈이 주입되는지, 빈의 이름이 무엇인지 한눈에 파악하기 힘들다.
    이럴 때는 별도의 설정 정보를 만들어 한눈에 파악하기 좋게 만든다.

    자동 빈 등록을 사용하는 경우에는 해당 빈들을 특정 패키지에 같이 묶어 한눈에 파악하기 좋게 만든다.

 

 

 

 

 

 

출처 : 인프런 김영한 님 스프링 강의

반응형

'내가 공부하려고 올리는 > 스프링' 카테고리의 다른 글

빈 스코프  (0) 2022.02.06
빈 생명 주기 콜백  (0) 2022.02.04
컴포넌트 스캔  (0) 2022.02.03
싱글톤 컨테이너  (0) 2022.02.02
스프링 컨테이너와 스프링 빈  (0) 2022.02.02

댓글