반응형

 

 

"김영한 스프링 핵심 원리 기본편" 내용을 기반으로 작성한 내용입니다.

 

스프링 핵심 원리 - 기본편 강의 | 김영한 - 인프런

김영한 | 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보

www.inflearn.com

 

📝의존관계 주입 (@Autowired)

의존관계 주입은 크게 4가지가 있습니다.

  • 생성자 주입
  • 수정자 주입 (setter)
  • 필드 주입
  • 일반 메서드 주입 (많이 안 쓰기 때문에 패스)

 

생성자 주입

@Component
public class OrderServiceImpl implements OrderService {

  private final MemberRepository memberRepository;
  private final DiscountPolicy discountPolicy;

  @Autowired // 생성자 1개일 때 생략 가능
  public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy
      discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
  }
}

생성자를 통해 의존관계를 주입 받습니다. 생성자가 딱 1개만 있으면 @Autowired를 생략해도 자동 주입이 됩니다. 여기에선 빈으로 생성된 MemberRepository, DiscountPolicy가 의존관계 주입되어 들어가게 됩니다.

 

 

수정자 주입

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

setter를 통해 의존관계를 주입 받습니다. 여기에선 빈으로 생성된 MemberRepository, DiscountPolicy가 의존관계 주입되어 들어가게 됩니다.

 

 

필드 주입

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

 

필드에 바로 주입하는 방법입니다. 코드가 간별하지만 외부에서 변경이 불가능해서 테스트 하기 어렵다는 단점이 있다. 예를 들면 setDiscountPolicy, setMemberRepository같은 Setter가 존재하면 DI 컨테이너(스프링)를 따로 안 띄워도 직접 넣어서 테스트할 때 사용이 가능한데 필드만 있는 경우는 스프링을 다 띄우지 않는 이상 테스트하기 어렵다.

 

 

📝어떤 걸 사용해야할까?

생성자 주입을 사용해야합니다. 필드 주입의 경우 테스트가 어려우며 Setter의 경우 변경가능성이 있기 때문에 객체 생성시 1번만 호출되게 설계할 수 있습니다.(불변) 또한 순수 자바코드로 단위테스트도 가능합니다. 

 

final 키워드 사용시 생성자에 값이 없으면 컴파일 오류를 발생시켜 장애를 방지할 수 있습니다.

 

 

📝빈이 2개 이상일 때 해결 (@Qulifier, @Primary)

@Autowired

  1. 타입 매칭
  2. 타입 매칭의 결과가 2개이상일 때 필드명, 파라미터명으로 빈 이름 매칭
@Autowired
private DiscountPolicy rateDiscountPolicy

DiscountPolicy를 상속받은 RateDiscountPolicy, FixDiscountPolicy가 있으면 위 코드와 같이 했을 때 필드명을 보고 주입하게 된다. 즉, RateDiscountPolicy를 주입하게 된다.

 

 

@Qualifier

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

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

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

Qualifier의 경우 @Qualifier에 등록한 Bean이름을 찾아서 주입합니다.

 

// 어노테이션 생성
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier("mainDiscountPolicy")
	public @interface MainDiscountPolicy {
}


@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {}

위 같이 @Qualifier지정할 때 따로 어노테이션을 따로 만들어서 사용할 수 있는데 이렇게 사용하면 컴파일시기에 에러를 잡아낼 수 있는 장점이 있다.

 

@Primary

@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}

@Component
public class FixDiscountPolicy implements DiscountPolicy {} 

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

Primary의 경우 여러개 빈이 있어도 Primary 등록한 최상위 것이 먼저 등록됩니다. Qualifier의 경우 코드가 지저분해지기 때문에 @Primray를 더 잘 사용합니다.

 

📝등록된 Bean 다 가져오기

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);
    }
    
    static class DiscountService {
        private final Map<String, DiscountPolicy> policyMap;
        private final List<DiscountPolicy> policies;
        
        public DiscountService(Map<String, DiscountPolicy> policyMap,
        List<DiscountPolicy> policies) {
            this.policyMap = policyMap;
            this.policies = policies;
            System.out.println("policyMap = " + policyMap);
            System.out.println("policies = " + policies);
        }
        
        public int discount(Member member, int price, String discountCode) {
            DiscountPolicy discountPolicy = policyMap.get(discountCode);
            System.out.println("discountCode = " + discountCode);
            System.out.println("discountPolicy = " + discountPolicy);
        	return discountPolicy.discount(member, price);
        }
    }
}

DiscountPolicy 인터페이스를 상속받은 고정할인(fixDiscountPolicy)하고 퍼센티지할인(rateDiscountPolicy)이 Bean으로 만들어져있습니다. 여기에 사용자가 넘긴 정보에 따라 고정 또는 퍼센티지를 결정해야할 경우 DiscounyPolicy구현체인 Bean으로 만들어진 걸 가져와서 넘긴 파라미터에 따라 다르게 처리할 수 있습니다.

 

Map의 경우 키에 Bean의 이름이 들어가고 값에는 구현체가 들어가게 됩니다. List의 경우는 키값이 없기 때문에 구현체만 들어가게 됩니다.

 

📝 수동 등록 vs 자동 등록

수동등록

@Configuration
public class DiscountPolicyConfig {

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

Configuration을 쓰고 Bean을 그 안에서 직접 생성해서 관리합니다. 한 곳에 모여있기 때문에 보기가 편합니다.

 

 

자동등록

@Component
public class DiscountPolicy {

}

Component어노테이션 기반으로 만들어진 어노테이션을 붙이면 Component Scan범위에 있으면 찾아서 Bean 등록해줍니다

 

요즘은 자동등록을 많이 쓰지만 수동등록의 경우도 필요할 때 써야한다. 보통 업무로직(Controller, Service, Repository)의 경우 자동 등록을 사용하고 기술지원(로깅, AOP 등)의 경우 Configuration으로 수동관리하는 편이다.

 

 

 

 

반응형