본문 바로가기
WEB/Spring

[Spring] 의존관계 주입(Dependency Injection) 개념과 Bean 중복방지

by 정권이 내 2023. 12. 14.

[Spring] 의존관계 주입(Dependency Injection) 개념과 Bean 중복방지

 

의존관계 주입(Dependency Injection) 방법

스프링에서 의존관계 주입 방법은 크게 4가지가 있지만 스프링에서 권장하는 방식인 생성자 주입 방식만 잘 알고있으면 됩니다.

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

 

예제 코드

생성자 주입의 특징은 생성자 호출시점에 단 한 번만 호출되는 것이 보장되고 불변, 필수적인 의존관계에 사용합니다. 단일 생성자만 있다면 @Autowired는 생략할 수 있습니다.

@Service
public class UserServiceImpl implements UserService {

    private final UserRepository userRepository;

    // @Autowired 생략가능
    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
	...
	...
}

 

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

  • 대부분의 의존관계는 애플리케이션 종료시까지 변경될 일이 없는것이 좋다.
  • 수정자 주입은 public 메서드를 사용하기 때문에 외부에서 변경이 가능하므로 실수를 유발할수 있다.
  • 생성자주입은 객체 생성시 한번만 호출되므로 불변하게 설계하는것이 가능하다.
  • 생성자 주입시 final키워드를 사용하므로 실수로인한 컴파일 오류를 파악할수 있다.

결론적으로 생성자 주입을 선택하는 가장 큰 이유는 스프링 프레임워크에 의존하지 않고 순수 자바언어의 특징을 살리는 방법이기 때문입니다.

 

생성자 주입 사용 방법

위 예제코드에서 볼수 있듯이 Spring 4.3버전 이후로 클래스에 단일 생성자만 존재하면 @Autowired 어노테이션을 생략하더라도 Bean 객체로 등록되고 Lombok 라이브러리에서 제공하는 @RequiredArgsConstructor 어노테이션을 사용한다면 final로 선언된 필드와 @NonNull이 붙은 필드에 대해 생성자를 생성합니다.

@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {

    private final UserRepository userRepository;
	...
	...
}

 

Bean 객체 중복 방지

예제 코드로 Bean 객체가 중복 되는 상황에 대해 알아보겠습니다. 아래의 코드를 보면 Bean 객체인 DiscountPolicy를 OrderService 클래스에서 호출하고 있는데 DiscountPolicy 인터페이스는 RateDiscountPolicy, FixDiscountPolicy 클래서에서 상속되고 있습니다.

@Component
@RequiredArgsConstructor
public class OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;
}
@Component
public class RateDiscountPolicy implements DiscountPolicy {
	...
}
...
@Component
public class FixDiscountPolicy implements DiscountPolicy {
	...
}

 

NoUniqueBeanDefinitionException: No qualifying bean of type 'com.example.springdemostudy.discount.DiscountPolicy' available: expected single matching bean but found 2: fixDiscountPolicy,rateDiscountPolicy

RateDiscountPolicy, FixDiscountPolicy 두 클래스는 스프링 컨테이너에서 동일하게 DiscountPolicy 타입의 Bean객체로 관리 되는데 OrderService 클래스에서 Bean 객체 호출시 둘중에 어떤 Bean객체를 사용할건지 명시하지 않았기 때문에 오류가 발생합니다.

Bean객체를 명시적으로 호출하기 위해서는 필드명 매칭과 @Qualifier, @Primary 어노테이션을 사용하는 방법이 있습니다.

 

필드명 매칭

같은 타입의 Bean 객체가 중복으로 스프링 컨테이너에 등록될경우 객체 타입을 먼저 매칭하고 중복발생시 필드명 매칭을 시도합니다.

@Component
@RequiredArgsConstructor
public class OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy rateDiscountPolicy; // => RateDiscountPolicy
}

 

@Qualifier

필드명 매칭 방식은 코드는 간결하지만 만약 필드명이 변경될경우 참조하는곳을 모두 수정해야 하는 불편함이 있습니다. @Qualifier 어노테이션은 필드명 변경과 무관하게 명시적으로 의존관계를 주입할 Bean 객체를 지정합니다.

@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {
	...
}

@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {
	...
}
@Component
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
	...
}

RateDiscountPolicy 클래스 @Qualifier 애노테이션에 mainDiscountPolicy구분자를 지정하였고 의존관계를 주입받는 OrderServiceImpl 클래스의 생성자 메서드에서 DiscountPolicy 객체 앞에 @Qualifier("mainDiscountPolicy")를 붙여서 의존관계를 주입받습니다.

주의할점은 생성자 생성자에 @Qualifier("mainDiscountPolicy")를 지정해도 @Qualifier를 사용한 클래스가 없어서 찾지 못할경우 스프링 빈에서 mainDiscountPolicy 이름을 찾아 주입받게 되는데 이는 의도하지 않은 의존관계 주입을 발생시킬수 있습니다. 따라서 반드시 매칭되도록 구분자를 명시해야 합니다.

 

@Primary

@Primary 어노테이션을 선언하면 스프링 컨테이너에서 해당 Bean 객체를 우선적으로 의존관계 주입 받을수 있도록 합니다. @Qualifier는 구분자 이름도 지정해야하고 생성자 파라미터에 명시적으로 선언해야 하지만 @Primary는 편리하게 사용할수 있다는 장점이 있습니다.

@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {
	...
}
...
@Component
public class FixDiscountPolicy implements DiscountPolicy {
	...
}
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;
	...
}

@Primary가 선언된 RateDiscountPolicy 클래스가 우선적으로 의존관계 주입에 사용됩니다.

 

 

연관 포스팅

반응형

댓글