[Spring] 컴포넌트 스캔과 의존 관계 자동 주입

Narcoker·2023년 8월 13일

Spring

목록 보기
8/19

컴포넌트 스캔과 의존 관계 자동 주입

이전에 스프링 빈을 등록할 때는 자바 코드의 @Bean이나 XML의 등을 통해서
설정 정보에 직접 등록할 스프링 빈을 나열했다.

아래 예제에서는 몇개가 안되었지만,
이렇게 등록해야 할 스프링 빈이 수십, 수백개가 되면 일일이 등록하기도 귀찮고,
설정 정보도 커지고, 누락하는 문제도 발생한다.

package hello.core;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.member.MemberRepository;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemberService;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {
    @Bean
    public MemberService memberService(){
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    private static MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    @Bean
    public OrderService orderService(){
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    @Bean
    public DiscountPolicy discountPolicy(){
        return new FixDiscountPolicy();
    }
}

그래서 스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공한다.
또 의존관계도 자동으로 주입하는 @Autowired 라는 기능도 제공한다.

컴포넌트 스캔을 사용하려면 먼저 @ComponentScan 을 설정 정보에 붙여주면 된다.
기존의 AppConfig와는 다르게 @Bean으로 등록한 클래스가 하나도 없다

package hello.core;

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

import static org.springframework.context.annotation.ComponentScan.*;

@Configuration
@ComponentScan(
        // @ComponentScan 을 사용하면 @Component이 붙은 다른 AppConfig 설정 파일도 같이 스캔한다. 따라서 다른 파일들은 스캔 대상에서 지워줘야한다.
        // @Configuration 내부에 @Component 가 붙어 있다.
        // 기존에 존재하던 설정 파일인 AppConfig.java 파일을 스캔대상에서 제외하기 위한 설정
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
@ComponentScan(
        excludeFilters = @Filter(type = FilterType.ANNOTATION, classes =
                Configuration.class))

// AppConfig 와 달리 @Bean 이 없다.
public class AutoAppConfig {}

컴포넌트 스캔 대상 설정

이제 각 클래스가 컴포넌트 스캔의 대상이 되도록 @Component 애노테이션을 붙여주자.

MemoryMemberRepository @Component 추가

package hello.core.member;

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

RateDiscountPolicy @Component 추가

package hello.core.discount;

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

@Component // 컴포넌트 스캔 대상 설정 추가
public class RateDiscountPolicy implements DiscountPolicy{

    private int discountPercent = 10;

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

MemberServiceImpl @Component, @Autowired 추가

package hello.core.member;

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

@Component // 컴포넌트 스캔 대상 설정 추가
public class MemberServiceImpl implements MemberService{

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

OrderServiceImpl @Component, @Autowired 추가

package hello.core.order;

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


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

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

컴포넌트 스캔 동작 방식


@ComponentScan@Component 가 붙은 모든 클래스를 스프링 빈으로 등록한다.
이때 스프링 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자만 소문자를 사용한다

빈 이름 기본 전략: MemberServiceImpl 클래스 memberServiceImpl
빈 이름 직접 지정: 만약 스프링 빈의 이름을 직접 지정하고 싶으면
@Component("memberService2") 이런식으로 이름을 부여하면 된다.

의존 관계 자동 주입 동작 방식


생성자에 @Autowired 를 지정하면,
스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다.

이때 기본 조회 전략은 타입이 같은 빈을 찾아서 주입한다.

getBean(MemberRepository.class) 와 동일하다고 이해하면 된다.

profile
열정, 끈기, 집념의 Frontend Developer

0개의 댓글