본문 바로가기

공부/Spring 핵심 원리

컴포넌트 스캔으로 의존 관계를 자동 주입해보자.

 

 

 

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

- 탐색 위치와 기본 스캔 대상

- 필터

- 중복 등록과 충돌

 

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

 지금까지의 예제는 스프링 빈을 등록 시 설정 정보에 직접 등록할 스프링 빈을 나열했다. 스프링 빈이 많아지면 등록이나 관리가 힘들어지는데 더 나은 방법이 없을까?

 

👧 컴포넌트 스캔 기능을 사용해보자!

 

✅ 빈을 찾는 @ComponentScan, 빈을 자동 주입 해주는 @Autowired

더보기
@Configuration

@ComponentScan(
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Configuration.class)
)
public class AutoAppConfig {

}
더보기
package com.example.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);
    }


}
더보기
package order;

import com.example.core.member.Member;
import com.example.core.member.MemberRepository;
import discount.DisCountPolicy;
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가 붙은 클래스를 찾아 자동으로 Bean으로 등록해 준다.

- @Autowired가 해당하는 타입에 맞는 빈을 찾아 자동으로 의존 관계를 주입한다.

 

📌 컴포넌트 스캔과 자동 의존관계 주입 동작 과정이 어떻게 되나?

 

✅ 빈들을 스캔, @ComponentScan

- @ComponentScan은 @Component가 붙은 클래스를 스프링 빈으로 등록한다.

- 스프링 빈의 기본 이름은 클래스 명의 맨 앞글자만 소문자로 사용된다. (MemberServiceImpl : memberServiceImpl)

- 빈을 직접 지정하고 싶으면 @Component("사용하고 싶은 빈 이름 지정")로 부여할 수 있다.

 

✅ 스캔한 빈들을 자동 주입 해주는 @Autowired 

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

 

📌컴포넌트 스캔의 탐색 위치와 기본 스캔 대상을 지정해 봅시다.

 

✅ 탐색할 패키지의 시작 위치 지정

@Configuration

@ComponentScan(
        basePackages = "com.example.core.member",
        basePackageClasses = AutoAppConfig.class,
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Configuration.class)
)
public class AutoAppConfig {

}

✔ basePackages

 - 탐색할 패키지의 시작 위치를 지정 한다. 이 패키지를 포함해서 하위 패키지를 모두 탐색한다.

✔  basePackageClasses

  - 지정한 클래스의 패키지를 탐색 시작 위치로 지정한다.

  - 지정 안 하면, @ComponentScan 이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.

 

✅ 컴포넌트 스캔 대상 권장 방법

패키지 위치를 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트 최상단에 둔다.

 

 

- AutoAppConfig : com.example.core -> 프로젝트 시작 루트

- com.exmple.core.discount

- com.exmple.core.member

- com.exmple.core.order

 

 

✅ 컴포넌트 스캔 기본 대상

 - 컴포넌트 스캔은 @Component 뿐 아니라 아래의 대상도 포함된다.

@Component 컴포넌트 스캔에서 사용
@Controller 스프링 MVC 컨트롤러에서 사용
@Service 스프링 비즈니스 로직에서 사용, 특별한 처리는 없고, 비즈니스 계층을 인식하는데 도움이 됌
@Repository 스프링 데이터 접근 계층에서 사용, 데이터 계층의 예외를 스프링 예외로 변환 해줌
@Configuration 스프링 설정 정보에서 사용, 스프링 빈이 싱글톤을 유지하도록 추가 처리를 한다.

 

@Service에 @Component 되있음

 

 

 

✅ 어노테이션을 생성해서 컴포넌트 스캔 대상을 정해보자.

 

✔ includeFilters : 컴포넌트 스캔 대상을 추가로 지정한다.

✔ excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정한다.

 

 

- 컴포넌트 스캔에서 추가할 어노테이션 생성

더보기
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}

- 추가할 빈 등록

더보기
@MyIncludeComponent
public class BeanA {
}

- 컴포넌트 스캔에서 제외할 어노테이션 생성

더보기
package scan.filter;


import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {
}

- 제거할 빈 등록

더보기
package scan.filter;

@MyExcludeComponent
public class BeanB {
}

-테스트 코드 작성

더보기
public class ComponentFilterAppConfigTest {

    @Test
    void filterScan(){
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);
        BeanA beanA = ac.getBean("beanA", BeanA.class);
        Assertions.assertThat(beanA).isNotNull();


        assertThrows(
                NoSuchBeanDefinitionException.class,
                () -> ac.getBean("beanB", BeanB.class)

        );
    }

    @Configuration
    @ComponentScan(
            includeFilters = @Filter(type= FilterType.ANNOTATION, classes = MyIncludeComponent.class),
            excludeFilters = @Filter(type = FilterType.ANNOTATION,classes = MyExcludeComponent.class)
    )
    static class ComponentFilterAppConfig{

    }

}

 

- beanA는 등록됬지만, beanB는 스캔되지 않아 등록되지 않았다.

 

✅ 컴포넌트 스캔의 FilterType 옵션

ANNOTATION 기본값, 애노테이션을 인식해서 동작한다.
ASSIGNABLE_TYPE 지정한 타입과 자식 타입을 인식해서 동작한다.
ASPECTJ aspectJ패턴 사용
REGEX 정규표현식
CUSTOM TypeFilter 인터페이스를 구현해서 처리

 

 

📌 컴포넌트 스캔에서 같은 빈 이름을 등록하면 어떻게 될까?

 

자동 빈 등록 vs 자동 빈 등록

- 컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록될 때 이름이 같은 경우 스프링은 오류를 발생시킨다.

- ConflictingBeanDefinitionException 예외 발생

 

수동 빈 등록 vs 자동 빈 등록

 - 수동 빈 등록과 자동 빈 등록에서 빈 이름이 충돌한다면?

 

@Configuration

@ComponentScan(
        basePackages = "com.example.core.member",
        basePackageClasses = AutoAppConfig.class,
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Configuration.class)
)
public class AutoAppConfig {

    @Bean(name = "memoryMemberRepository")
    MemoryMemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }

}

 

👧 성공했는데 이유는 뭘까

 수동 빈 등록이 우선권을 가지기 때문이다.

 자동 보다는 수동이 우선권을 가지는 것은 좋지만.. 개발자가 의도적으로 설정했다기 보다는, 여러 설정이 꼬여 결과가 만들어 지는 경우가 대부분이다.

 

👧 최근에는 스프링 부트에서는 수동 빈 등록과 자동 빈 등록이 충돌나면 오류가 발생하게 기본값을 설정이 되있다.

 

 

 

REFERENCE

 


https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

 

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

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 개발자가 되어보세요! 📣 확인해주

www.inflearn.com

 

 

 

 

 

300x250