7. 의존관계 자동 주입

jinhxxxxkim·2023년 2월 6일
0

Spring핵심

목록 보기
7/9
post-thumbnail

7. 의존관계 자동 주입

1. 다양한 의존관계 주입 방법

  1. 생성자 주입
  2. 수정자(setter) 주입
  3. 필드 주입
  4. 일반 메소드 주입

생성자 주입

  • 생성자를 통해해서 의존관계를 주입 받는 방법이다.
  • 지금까지 사용한 의존관계 주입 방법이다.
  • 특징
    1. 생성자 호출 시점 딱 1번만 호출 되는 것이 보장된다.
    2. 불변, 필수 의존관계에 사용한다.
  • 즉, 의존관계 주입에 한계를 설정하여 외부에서 의존관계 변경을 방지한다.
  • 스프링 컨테이너에 빌딩되어 올라갈 때, 연관관계를 정의하고 마무리하겠다는 의미이다.
@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;
    }
    ...
}
  • 컴포넌트 스캔에 의해 @Component가 달리 클래스를 빈에 등록한다.
  • 빈에 등록하기 위해 객체 생성을 진행하게 되는데, 객체 생성을 위해 생성자를 호출한다.
  • 생성자에 달린 @Autowired를 확인하고 스프링 컨테이너에서 필요한 빈을 주입한다.
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
  • 필드를 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를 생략해도 자동 주입 된다. 물론 스프링 빈에만 해당한다.
  • 생성자가 2개 이상이라면, 어떤 생성자를 선택할지 @Autowired를 통해 지정해주어야 한다.

수정자(setter) 주입

  • setter라 불리는 필드의 값을 변경하는 메소드를 통해 의존관계를 주입하는 방법
  • 특징
    1. 선택, 변경 가능성이 있는 의존관계에 사용한다
    2. 자바빈 프로퍼티 규약의 수정자 메소드 방식을 사용한다.
  • 만일, 필요한 빈이 컨테이너에 등록이 안되어있을 경우에도 사용할 수 있다.
    • @Autowired(required = false)로 지정하여야 한다.
  • 중간에 인스턴스를 변경하고 싶다면, 외부에서 수정자를 호출하면 된다.
@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;
    }  
    ...
}
  • 필드의 수정자를 통해 변경 가능하므로 final 키워드가 없다.
  • 스프링 빈을 생성자를 통해 등록한 후, 수정자(setter)를 통해 의존관계를 주입한다
  • 생성자 주입의 경우, 생성자 호출과 동시에 의존관계 주입이 완료된다.
  • 하지만, 수정자 주입의 경우, 빈을 등록한 후, 주입이 일어난다.

필드 주입

  • 필드에 바로 의존관계를 주입하는 방법
  • 일반적으로 잘 사용하지 않는다.
  • 단점
    • 외부에서 변경할 수 없어 테스트 하기 어렵다
    • DI 프레임워크가 없으면 아무 것도 할 수없다.
  • 스프링 설정을 목적으로 하는 @Configuration 같은 곳에서만 특별한 용도로 사용한다.
  • 애플리케이션의 실제 코드와 관계 없는 테스트 코드에서 사용한다.
@Component
public class OrderServiceImpl implements OrderService{

    @Autowired private MemberRepository memberRepository;
    @Autowired private DiscountPolicy discountPolicy;
    ...
}
  • 순수한 자바 테스트 코드에는 @Autowired가 동작하지 않는다. @SpringBootTest 처럼 스프링 컨테이너를 테스트에 통합한 경우에만 가능하다.
@Test
void fieldInjectionTest(){
	OrderServiceImpl orderService = new OrderServiceImpl();
    orderService.createOrder(1L, "itemA", 10000);
}
  • 자바로 작성한 테스트 코드에서는 실행할 수 없다.
  • 스프링 컨테이너를 사용하지 않았기에 필드 값 memberRepositorydiscountPolicy는 모두 null이다.
  • 따라서 memberRepository에 접근할 경우 NPE가 발생할 수 밖에 없다.

일반 메소드 주입

  • 일반 메소드를 통해 의존관계를 주입 받을 수 있다.
  • 한번에 여러 필드를 주입 받을 수 있다.
  • 일반적으로 잘 사용하지 않는다.
@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;
 	}
}

2. 옵션 처리

  • @Autowired를 사용하여 의존관계를 주입할 때 컨테이너에 아직 등록되지 않은 빈이 있을 수 있다.
  • 따라서, 스프링 빈이 없이 동작해야할 때가 있다.
org.springframework.beans.factory.UnsatisfiedDependencyException: ...
  • @Autowired만 사용하면 required옵션의 기본 값이 true이므로, 자동 주입 대상이 없으면 UnsatisfiedDependencyException가 발생한다.
  • 따라서 필요에 따라 주입 대상을 옵션으로 처리해야한다.

자동 주입 대상을 옵션으로 처리하는 방법

  1. @Autowired(required=false) : 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출 안된다.
  2. org.springframework.lang.@Nullable : 자동 주입할 대상이 없으면 null이 입력된다.
  3. Optional<> : 자동 주입할 대상이 없으면 Optional.empty 가 입력된다.

1. @Autowired(required=false)

코드

@Autowired(required = true)
public void setNoBean1(Member noBean1) {
	System.out.println("noBean1 = " + noBean1);
}

결과

// 아무것도 출력되지 않는다
  • Member는 스프링 빈이 아니므로, 컨테이너에 등록되어있지 않다.
  • 따라서 @Autowired로 되어있는 메소드 자체를 호출하지 않는다.

2. org.springframework.lang.@Nullable

@Autowired
public void setNoBean2(@Nullable Member noBean2){
	System.out.println("noBean2 = " + noBean2);
}

결과

noBean2 = null
  • Member는 스프링 빈이 아니므로, 컨테이너에 등록되어있지 않다.
  • @Autowired(required=false)와 달리, null을 반환한다.

3. Optional<>

@Autowired
public void setNoBean3(Optional<Member> noBean3) {
	System.out.println("noBean3 = " + noBean3);
}

결과

noBean3 = Optional.empty
  • Member는 스프링 빈이 아니므로, 컨테이너에 등록되어있지 않다.
  • Optional클래스의 empty를 반환한다.

@Nullable, Optional은 스프링 전반에 걸쳐서 지원된다.
예를 들어서 생성자 자동 주입에서 특정 필드에만 사용해도 된다.

3. 생성자 주입의 선택

최근 스프링을 포함한 DI 프레임워크 대부분이 생성자 주입을 권장한다.

불변

  • 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료시점까지 의존관계를 변경할 일이 없다.
  • 오히려 대부분의 의존관계는 애플리케이션 종료 전까지 변하면 안된다.(불변해야 한다.)
  • 수정자 주입을 사용하면, setXxx 메서드를 public으로 열어두어야 한다.
  • 누군가 실수로 변경할 수 도 있고, 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방법이 아니다.
  • 생성자 주입은 객체를 생성할 때 딱 1번만 호출되므로 이후에 호출되는 일이 없다. 따라서 불변하게 설계할 수 있다.

누락

@Test
void creatOrder(){
	OrderServiceImpl orderService = new OrderServiceImpl();
    Order order = orderService.createOrder(1L, "itemA", 10000);
    assertThat(order.getDiscountPrice()).isEqualTo(1000);
}
  • 위와 같이 자바코드로 단위 테스트를 진행하는 경우, 수정자(setter) 주입을 사용하게 된다면, NPE가 발생한다.
  • discountPolicy, memberRepository에 대한 의존관계가 누락되었기 때문이다.
  • 만일 생성자 주입을 사용하게 된다면, 주입 데이터가 누락되었을 경우, 컴파일 오류가 발생한다.

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;
 	}
 	...
}
  • 생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있다.
  • 따라서 생성자에서 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에 막아준다.
  • 수정자 주입을 포함한 나머지 주입 방식은 모두 생성자 호출 이후에 호출되므로, 필드에 final 키워드를사용할 수 없다. 오직 생성자 주입 방식만 final 키워드를 사용할 수 있다.

생성자 주입 사용의 이유

  • 프레임워크에 의존하지 않고, 순수한 자바 언어의 특징을 잘 살리는 방법이다.
  • 기본으로 생성자 주입을 사용하고, 필수 값이 아닌 경우에는 수정자 주입 방식을 옵션으로 부여하면 된다.
    • required = false
    • @Nullable
    • Optional<>
  • 생성자 주입과 수정자 주입을 동시에 사용할 수 있다.

라이브 코딩 도중 오류

Test 코드

@Test
void creatOrder(){
	MemoryMemberRepository memberRepository = new MemoryMemberRepository();
    memberRepository.save(new Member(1L, "name", Grade.VIP));

    OrderServiceImpl orderService = new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
    Order order = orderService.createOrder(1L, "itemA", 10000);
    
    assertThat(order.getDiscountPrice()).isEqualTo(1000);
}
  • line3, 4에서 MemoryMemberRepository의 객체를 생성하였다.
  • line6에서 OrderServiceImpl의 객체를 생성할 때, 생성자의 파라미터로 새로운 MemoryMemberRepository객체를 생성하여 전달하였다.
  • 두개의 MemoryMemberRepository객체가 생성되었는데 Member 조회가 어떻게 되는지 고민하였다.
@Component
public class MemoryMemberRepository implements MemberRepository{

    private static final Map<Long, Member> store = new HashMap<>();
    ...
}
  • MemoryMemberRepository를 보면, 저장소가 static으로 선언되어있다.
  • static으로 선언되었다는 것을 잊고, 서로 다른 MemoryMemberRepository에 접근하였음에도 서로 다른 저장소에 접근 하는 것으로 생각하였다.

4. 롬복과 최신 트랜드

코드 최적화

기존 코드

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

@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;
 	}	
    ...
}
  • 생성자가 딱 1개만 있으면 @Autowired 를 생략할 수 있다

Lombok 적용

@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
 	private final MemberRepository memberRepository;
 	private final DiscountPolicy discountPolicy;
    ...
}
  • 롬복 라이브러리가 제공하는 @RequiredArgsConstructor 기능을 사용하면 final이 붙은 필드를 모아서 생성자를 자동으로 만들어준다
  • 롬복이 자바의 애노테이션 프로세서라는 기능을 이용해서 컴파일 시점에 생성자 코드를 자동으로 생성해준다

5. 조회 빈이 2개 이상 - 문제

  • @Autowired 는 타입(Type)으로 조회한다
@Autowired
private DiscountPolicy discountPolicy{...}
  • 타입으로 조회하기 때문에, 마치 다음 코드와 유사하게 동작한다.
    ac.getBean(DiscountPolicy.class)
  • 타입으로 조회하면 선택된 빈이 2개 이상일 때 문제가 발생한다.
@Component
public class FixDiscountPolicy implements DiscountPolicy {...}
@Component
public class RateDiscountPolicy implements DiscountPolicy {...}
  • 위와 같이 DiscountPolcy 타입의 빈 2개를 등록하였다.
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService{

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;
}
  • 그 후, Lombok을 사용하여 의존관계를 주입하면 다음과 같은 오류가 발생한다.
...
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type 'hello.core.discount.DiscountPolicy' available: 
expected single matching bean but found 2: fixDiscountPolicy,rateDiscountPolicy
...
  • NoUniqueBeanDefinitionException 오류가 발생한다.
  • 1개의 빈이 matching 될 것이라 예상하였지만, fixDiscountPolicy, rateDiscountPolicy 총 2개의 빈이 matching되었다.
  • 이때 하위 타입으로 지정할 수 도 있지만, 하위 타입으로 지정하는 것은 DIP를 위배하고 유연성이 떨어진다.
  • 이름만 다르고, 완전히 똑같은 타입의 스프링 빈이 2개 있을 때 해결이 안된다.

6. @Autowired 필드 명, @Qualifier, @Primary

조회 대상 빈이 2개 이상일 때 해결 방법

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

@Autowired 필드 명 매칭

기존코드

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

필드 명을 빈 이름으로 변경

@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy fixDiscountPolicy) {
	this.memberRepository = memberRepository;
    this.discountPolicy = fixDiscountPolicy;
}
  • 필드 명 매칭의 순서
    1. 타입 매칭 시도
    2. 타입 매칭의 결과가 2개 이상일 경우, 필드 명(필드 주입), 파라미터 명(생성자 주입 etc.)으로 빈 이름 매칭

@Qualifier

  • @Qualifier 는 추가 구분자를 붙여주는 방법이다. 주입시 추가적인 방법을 제공하는 것이지 빈 이름을 변경하는 것은 아니다.

빈 등록 시 @Qualifier 설정

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

의존관계 주입 시 @Qualifier 설정

@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
 		this.memberRepository = memberRepository;
 		this.discountPolicy = discountPolicy;
}
  • @Qualifier 로 주입할 때 @Qualifier("mainDiscountPolicy") 를 못찾을 경우 mainDiscountPolicy라는 이름의 스프링 빈을 추가로 찾는다.
  • 하지만, @Qualifier 를 찾는 용도로만 사용하는게 명확하고 좋다.
  • 수정자 주입, 직접 빈 등록 시에도 사용할 수 있다.

@Primary

  • @Primary는 우선순위를 정하는 방법이다.
  • @Autowired 시 여러 빈이 매칭 되면, @Primary가 우선권을 갖는다.

우선순위 설정

@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {...}
@Component
public class FixDiscountPolicy implements DiscountPolicy {...}
  • @Primary설정을 하면, 의존성 주입 시 수정할 부분이 없다.

@Primary, @Qualifier 활용

  • 2개의 스프링 빈이 존재한다
    • 스프링 빈1: 메인 DB에서 커넥션을 획득
    • 스프링 빈2: 서브 DB에서 커넥션을 획득
  • 스프링 빈1에는 @Primary를 적용
  • 스프링 빈2에는 @Qualifier를 적용
  • 메인 DB의 커넥션을 얻는 부분에서는 @Qualifier의 지정 없이 스프링 빈 조회
  • 메인 DB의 커넥션을 얻는 부분에서는 @Qualifier의 명시적인 지정으로 스프링 빈 조회

우선순위

  • 좁은 범위의 선택권인 Qualifier의 우선순위가 Primary의 우선순위보다 높다.

7. 어노테이션 직접 만들기

  • @Qualifier("mainDiscountPolicy")와 같이 문자를 적을 경우, 컴파일 타임에 오류를 잡을 수 없다.
  • 이러한 문제를 어노테이션을 만들어 해결할 수 있다

사용자 정의 어노테이션

@MainDiscountPolicy Annotation

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {}
  • 어노테이션을 정의할 때, @Qualifier 어노테이션을 추가한다.

RateDiscountPolicy.java

@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {...}
  • @Qualifier 대신에 @MainDiscountPolicy를 사용한다.

OrderServiceImpl.java

public class OrderServiceImpl implements OrderService{
	...
    @Autowired
    public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
        ...
    }
    ...
  • 생성자 또는 수정자의 매개변수 선언 위치에 @MainDiscountPolicy어노테이션을 추가한다.

사용자 정의 어노테이션의 개념

  • 애노테이션에는 상속이라는 개념이 없다.
  • 여러 애노테이션을 모아서 사용하는 기능은 스프링이 지원해주는 기능이다.
  • @Qulifier 뿐만 아니라 다른 애노테이션들도 함께 조합해서 사용할 수 있다.
  • 스프링이 제공하는 기능을 뚜렷한 목적 없이 무분별하게 재정의 하는 것은 유지보수에 더 혼란만 가중할 수 있다.

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

  • 해당 타입의 스프링 빈이 다 필요한 경우가 있다
  • 예를 들어서 할인 서비스를 제공하는데, 클라이언트가 할인의 종류(rate, fix)를 선택할 수 있는 경우가 있다.
  • 스프링을 사용하면 소위 말하는 전략 패턴을 매우 간단하게 구현할 수 있다.

코드

public class AllBeanTest {

    @Test
    void findAllBean(){
        ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);

        DiscountService discountService = ac.getBean(DiscountService.class);
        Member member = new Member(1L, "userA", Grade.VIP);
        int discountPrice = discountService.discount(member, 10000, "fixDiscountPolicy");
        assertThat(discountService).isInstanceOf(DiscountService.class);
        assertThat(discountPrice).isEqualTo(1000);

        int rateDiscountPrice = discountService.discount(member, 20000, "rateDiscountPolicy");
        assertThat(rateDiscountPrice).isEqualTo(2000);
    }

    static class DiscountService{
        private final Map<String, DiscountPolicy> policyMap;
        private final List<DiscountPolicy> policies;

        @Autowired
        public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {
            this.policyMap = policyMap;
            this.policies = policies;         
        }

        public int discount(Member member, int price, String discountCode) {
            DiscountPolicy discountPolicy = policyMap.get(discountCode);
            return discountPolicy.discount(member, price);
        }
    }
}

로직 설명

@Autowired
public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {
	this.policyMap = policyMap;
    this.policies = policies;         
}
  • DiscountService는 Map으로 모든 DiscountPolicy를 주입받는다.
  • Map에는 스프링 빈의 이름이 key, 객체가 value로 저장된다.
  • 따라서 Map의 타입은 StringDiscountPolicy다.
public int discount(Member member, int price, String discountCode) {
	 DiscountPolicy discountPolicy = policyMap.get(discountCode);
    return discountPolicy.discount(member, price);
}
  • DiscountServicediscount메소드는 매개변수로 Member, 가격, 적용할 할인 정책을 전달 받는다.
  • 적용할 할인 정책은 String타입으로 해당 할인 정책을 Map에서 조회하여 해당 객체를 반환한다.
  • 반환된 할인 정책 객체의 discount 메소드를 호출하여 할인된 가격을 최종 반환한다.
ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
  • 설정정보로 DiscountService.class뿐만 아니라, AutoAppConfig.class또한 전달하여 필요한 스프링 빈이 등록되도록 한다.
int discountPrice = discountService.discount(member, 10000, "fixDiscountPolicy");
int rateDiscountPrice = discountService.discount(member, 20000, "rateDiscountPolicy");
  • DiscountService클래스의 discount메소드를 호출 할 때 "fixDiscountPolicy"를 전달하면 DiscountServicepolicyMap을 조회하는데 key로 사용하게 된다.
  • 이와 같이"rateDiscountPolicy"를 전달하면 DiscountServicepolicyMap을 조회하는데 key로 "rateDiscountPolicy"를 사용하게 된다.

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

  • 편리한 자동 기능을 기본으로 사용하자

자동 주입

  • 스프링은 @Component 뿐만 아니라 @Controller , @Service , @Repository 처럼 계층에 맞추어 일반적인 애플리케이션 로직을 자동으로 스캔할 수 있도록 지원한다.
  • 스프링 부트는 컴포넌트 스캔을 기본으로 사용하고, 스프링 부트의 다양한 스프링 빈들도 조건이 맞으면 자동으로 등록하도록 설계했다.
  • 관리할 빈이 많아서 설정 정보가 커지면 설정 정보를 관리하는 것 자체가 부담이 된다.
  • 자동 빈 등록을 사용해도 OCP, DIP를 지킬 수 있다.

업무 로직 빈

웹을 지원하는 컨트롤러, 핵심 비즈니스 로직이 있는 서비스, 데이터 계층의 로직을 처리하는 리포지토리등이 모두 업무 로직이다. 보통 비즈니스 요구사항을 개발할 때 추가되거나 변경된다.

업무 로직은 숫자도 매우 많고, 한번 개발해야 하면 컨트롤러, 서비스, 리포지토리 처럼 어느정도 유사한 패턴이 있다. 이런 경우 자동 기능을 적극 사용하는 것이 좋다. 보통 문제가 발생해도 어떤 곳에서 문제가 발생했는지 명확하게 파악하기 쉽다.

기술 지원 빈

기술적인 문제나 공통 관심사(AOP)를 처리할 때 주로 사용된다. 데이터베이스 연결이나, 공통 로그 처리 처럼 업무 로직을 지원하기 위한 하부 기술이나 공통 기술들이다.

기술 지원 로직은 업무 로직과 비교해서 그 수가 매우 적고, 보통 애플리케이션 전반에 걸쳐서 광범위하게 영향을 미친다. 그리고 업무 로직은 문제가 발생했을 때 어디가 문제인지 명확하게 잘 드러나지만, 기술 지원 로직은 적용이 잘 되고 있는지 아닌지 조차 파악하기 어려운 경우가 많다. 그래서 이런 기술 지원 로직들은 가급적 수동 빈 등록을 사용해서 명확하게 드러내는 것이 좋다.

애플리케이션에 광범위하게 영향을 미치는 기술 지원 객체는 수동 빈으로 등록해서 설정 정보에 바로 나타나게 하는 것이 유지보수 하기 좋다.

비즈니스 로직 중에서 다형성을 적극 활용할 경우

DiscountService의 의존관계 자동 주입을 보게되면, Map<String, DiscountPolicy>에 주입을 받는다. 어떤 빈들이 주입될 지, 각 빈들의 이름은 무엇일지 코드만 보고 한번에 쉽게 파악할 수 없다. 자동 등록을 사용하고 있기 때문에 파악하려면 여러 코드를 찾아봐야 한다.

이런 경우 수동 빈으로 등록하거나 또는 자동으로하면 특정 패키지에 같이 묶어두는게 좋다. 핵심은 딱 보고 이해가 되어야 한다.

즉, 다형성을 적극 활용하는 비즈니스 로직은 수동 등록을 고민해보아야 한다.

수동 등록할 경우의 코드

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

설정 정보만 봐도 한눈에 빈의 이름은 물론이고, 어떤 빈들이 주입될지 파악할 수 있다. 그래도 빈 자동 등록을 사용하고 싶으면 파악하기 좋게 DiscountPolicy 의 구현 빈들만 따로 모아서 특정 패키지에 모아두는 것이 좋다.

출처: 인프런 스프링 핵심 원리 - 기본편 (김영한)
인프런 스프링 핵심 원리

0개의 댓글