반응형

컴포넌트 스캔


 

컴포넌트 스캔과 의존관계 자동주입 시작하기

이때까진 @Bean이나 을 사용하여서 설정정보에 직접 사용할 빈을 지정해줬다.

만약.. 등록해야되는 스프링빈이 수백개라면? ->설정정보도 커지고, 일일이 다 등록하기 귀찮고, 누락하는 문제도 발생될 수있다.

 

따라서 스프링을 사용하면 해결된다.

스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공!

 

그러면 설정정보가 없는데 의존관계는 어떻게 주입할까??...

바로 @Autowired로 의관관계를 자동으로 주입해준다.

 

기존 AppConfig.java는 그대로 두고 AutoAppConfig.java를 새로만든다.

 

package hello.core;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

//설정정보니까
@Configuration
//컴포넌트 스캔을 사용!!
//기존 AppConfig도 컴포넌트 스캔대상이므로 스프링 컨테이너에 등록되지 않게 해주어야한다.
//제외할 필터로 어노테이션 타입의 Configuration이 있는 클래스를 컴포넌트 스캔하지 않게 한다.
//왜냐면 AppConfig는 @Configuration 가 붙어있으니까 이거 빼줄라고
//그리고 AppConfig는 수동으로 빈을 등록하는거니까 자동 등록 테스트를 위해 빈으로 등록하지 않는다.
@ComponentScan( excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class))
public class AutoAppConfig {
    //아무 내용이 없어도 알아서 다 긁어서 스프링빈으로 찾아낸다.
}

 

 

자 이제 다시보면 (하다보니 주석에 다적어버려서 그냥 그대로 올려봣다.)

package hello.core;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

//설정정보니까
@Configuration
//컴포넌트 스캔을 사용!!
//기존 AppConfig도 컴포넌트 스캔대상이므로 스프링 컨테이너에 등록되지 않게 해주어야한다.
//제외할 필터로 어노테이션 타입의 Configuration이 있는 클래스를 컴포넌트 스캔하지 않게 한다.
//왜냐면 AppConfig는 @Configuration 가 붙어있으니까 이거 빼줄라고
//그리고 AppConfig는 수동으로 빈을 등록하는거니까 자동 등록 테스트를 위해 빈으로 등록하지 않는다.
@ComponentScan( excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class))
public class AutoAppConfig {
    //아무 내용이 없어도 알아서 다 긁어서 스프링빈으로 찾아낸다.
    //어떻게? => 위를 보면 일단 컴포넌트 스캔을 사용한다고 적었다.
    //그러면 컴포넌트들을 찾아야지! 어떻게? => @Component를 붙여서 알려주면됨!
    //빈으로 등록할 객체들을 컴포넌트로 등록!!
    //빈에는 구현체들이 등록이 되어있어야겟지? 그러면 구현체들에 @Component

    //그러면 스프링 컨테이너에 빈으로 등록은 되었다만... 어떻게 의존관계를 연결시키지... 하아..
    //바로 @Autowired 로 자동연결해주면 끄으읕 => 구현체에서 필요한 객체 인스턴스를 가져올 곳에 쓰면댐
}

/**
 * excludeFilters에 대해 다시 설명하면
 * @Configuration 어노테이션을 보면 @Component를 사용한다.!
 * 따라서 @Configuration이 있는 것도 다 긁어온다.
 * 그러므로 앞의 예제들에서 만든 AppConfig, TestConfig 등등 다 긁어와버리니까 제외시켜버렷다.
 *
 * (내 생각이긴한데 컴포넌트 스캔을 사용하는 설정파일은 AutoAppConfig뿐이니까
 * 자기 자신이 설정파일이지만 자기 자신을 제외한 나머지 설정들을 컴포넌트 스캔시 등록 안해버리는거같다.
 * 나중에 검색해봐야지...)
 */

 

즉, 컴포너트 스캔을 한다고 선언을 했다. 그러면 뭘 해야지??

컴포넌트 스캔이 찾을 수 있게 @Component라고 알려줘야댐

어디에?? => 스프링 빈으로 등록할 객체를!

어떤거지? => 실제 사용될 것들이니 실제 사용할 구현체이지

 

그러면 필요한 객체들을 @Component 을 붙여서 스프링 빈으로 등록을 했다.

킹치만.. 설정정보를 다시보면 아무내용도 없다. 그러면 의존관계 주입은 누가하냐???

@Autowired로 의존관계를 주입힌다.

어떻게? 해당 구현체가 필요한 곳에서!

다시 역할, 구현을 구분한다는거에 집중하자 => 배우(자기 역할)는 연기만하면 되지 상대배우(구현)가 누가될지는 몰라도 자기 역할만 하면된다.!

즉, 어떤 리포지토리가 쓰일진 모르지만, 그냥 리포지토리를 불러오면 된다! => 따라서 이러한 곳에 @Autowired로 의존관계 주입 !!

 

자 다시 정리하면, 구현체 들이 스프링빈으로 등록되어야하니

구현체(xxxIpml)들에 @Component!

그리고 구현체들이 다른 구현체가 필요할때 어떤 구현체가 올진 모르겟고 해당 역할만 필요로함! => @Autowired

 

package hello.core.member;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component //컴포넌트 스캔하여 빈으로 등록하기 위해서 알려줌
//멤버서비스 객체는 멤버리포지토리 객체가 필요함
public class MemberServiceImpl implements MemberService{

    //배역 배우로 생각할때
    //MemberRepository memberRepository = new MemoryMemberRepository();

    //생성자로 맞는 구현체 가져옴
    private final MemberRepository memberRepository;

    //리포지토리가 필요하네? 근데 어떤 저장소가 올진 내가 신경쓸게 아니지!
    //따라서 의존관계 주입!
    @Autowired
    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Override
    public void join(Member member) {
        memberRepository.save(member);
    }

    @Override
    public Member findMember(Long memberId) {
        return memberRepository.findById(memberId);
    }
}

package hello.core.member;

import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component//빈으로 등록하기 위해 컴포넌트라고 알려줌
//저장소 필요
public class MemoryMemberRepository implements MemberRepository{

    //일단 임시 저장소 = 메모리 사용
    private static Map<Long, Member> store = new HashMap<>();

    @Override
    public void save(Member member) {
        store.put(member.getId(),member);
    }

    @Override
    public Member findById(Long memberId) {
        return store.get(memberId);//store는 Map이므로 키값으로 member찾음
    }
}
package hello.core.discount;

import hello.core.member.Grade;
import hello.core.member.Member;
import org.springframework.stereotype.Component;

//일단 Fix할인 정책에서 Rate할인정책으로 바뀌였으니 비율할인 정책 구현체를 빈으로 등록
@Component
//10퍼센트만 할인 한다고 가정
public class RateDiscountPolicy implements DiscountPolicy{
    //할인율
    private int discountPrice = 10;

    @Override
    public int discount(Member member, int price) {
        if(member.getGrade() == Grade.VIP){
            return price*discountPrice/100;
        }
        else{
            return 0;
        }
    }
}

package hello.core.order;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.Member;
import hello.core.member.MemberRepository;
import hello.core.member.MemberService;
import hello.core.member.MemoryMemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


@Component //빈 등록
//주문을 생성해서 저장소에 vip인지 조회
//vip이면 할인 해줌
public class OrderServiceImpl implements OrderService{
    //저장소 조회
    //private final MemberRepository memberRepository = new MemoryMemberRepository();
    //할인을 위해서 할인 정책 필요
    //private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
    //private final DiscountPolicy discountPolicy= new RateDiscountPolicy();

    //생성자 주입기법으로 맞는 구현체 가져옴
    private final MemberRepository memberRepository;
    private DiscountPolicy discountPolicy;

    //역할, 구현 구분! 배우는 누가 캐스팅될지 모르고 걍 연기만 하면댐
    //여기선 어떤 리포지토리, 어떤 할인정책이 올지 모름
    //그냥 리포지토리를 쓰고 할인정책을 쓰면댐!!!
    //따라서 의존관계가 주입되어야되므로 @Autowired
    @Autowired
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }

    //id로 조회해서 vip 이면 할인정책 적용
    //주문 엔티티 => 회원id, 상품명, 상품가격, 할인된 금액
    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {
        Member member = memberRepository.findById(memberId);//회원 가져와서
        int discountPrice = discountPolicy.discount(member, itemPrice); //할인금액 적용

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }
}

 

자 이러면 이제 뭘해봐야지? => 테스트로 확인

테스트를 해보아서 진짜 다 스프링 빈으로 컨테이너에 등록이 되고 의존관계주입 즉 연결되었는지 확인 해보자!

 

package hello.core.scan;

import hello.core.AutoAppConfig;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class AutoAppConfigTest {

    @Test
    @DisplayName("컴포넌트 스캔으로 빈이 등록되었는지 확인, 의존관계도 확인")
    void basicScan(){
        //스프링 컨테이너에 빈들이 등록되었는지 확인 해보면된다.
        //설정정보로 스프링컨테이너 불러오고
        ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
        
        //요렇게하면 안된다. 왜냐면 안알랴줌... 뒤에서 설명해줄게...
//      MemberService memberService = ac.getBean("memberService", MemberService.class);

        //타입으로 찾음
        MemberService memberService = ac.getBean(MemberService.class);
        Assertions.assertThat(memberService).isInstanceOf(MemberService.class);
    }
}

 

 

위 주석에서 안알랴줌 이유를 설명하자면..

먼저 컴포넌트 스캔과 자동 의존관계 주입이 어떻게 동작하는지 봐야된다.

 

@Component 를 붙이면 이 어노테이션이 붙은 클래스들을 스프링 컨테이너에 스프링 빈으로 등록시킨다.

(스프링 컨테이너는 스프링 빈을 생성해서 가지고 있음)

등록 될땐, 자바빈 규약에 의해 xxxBean 와 같은 형식으로 빈이름이 지정된다.

(스프링 빈의 기본이름은 크래스 명을 사용하되, 맨 앞글자 소문자를 사용함)

예를 들면 @Component가 붙은 MemberServiceImpl을 보면 memberServiceImpl과 같이 빈이름이 설정된다.

 

자동 의존관계 주입은 @Autowired를 설정하면 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다.

(즉 역할만 알고 있으면댐 구현체가 뭔진 몰라도 알아서 가져다줌)

이때 해당 스프링 빈을 찾는 방식은 타입이 같은 빈을 찾아서 주입한다.

 

앞서 getBean(MemberRepositroy.class)를 생각해보자 이러면 구현 객체인 MemoryMemberRepository가 나왔다.

따라서 MemberRepositroy가 필요하면 스프링 컨테이너에서 MemberRepositroy타입으로 등록된 빈을 찾아서 주입해준다.

 

킹치만 같은 타입이 여러개라면?!?!?!?! => 충돌이 일어나겟지... 뒤에서 설명한다.

728x90
반응형
블로그 이미지

아상관없어

,