🍀spring/스프링 핵심원리 기본

[스프링 핵심 원리 - 기본] 컴포넌트 스캔

pkyung 2023. 2. 7. 00:10
반응형

 

인프런 김영한님 스프링 핵심 원리 강의를 듣고 정리한 글입니다. 

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

 

 

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

 

지금까지는 스프링 빈을 등록할 때 @Bean이나 <bean>등을 사용하여 직접 나열하였다. 

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

의존관계를 자동으로 주입하는 Autowired를 제공한다. 

 

컴포넌트 스캔을 사용하기 위해서는 @ComponentScan을 사용한다. 

기존 예제 코드를 남겨두기 위해 excludeFilters를 사용했다. 

(@Configuration안에 @Component가 들어있기 때문에 같이 조회가 된다.)

@Configuration
@ComponentScan(
        // 직접 주입한 AppConfig도 불려오기 때문에 빼준 것
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {

}

 

그리고 MemoryMemberRepository와 RateDiscountPolicy, MemberServiceImpl에 @Component 어노테이션을 추가한다.

그 후 의존관계 주입을 AutoAppConfig에서 할 수 없기 때문에 이곳에 Autowired로 의존관계를 주입한다. 

OrderServiceImpl 에도 Autowired를 추가한다.  

@Component
public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository;

    @Autowired // ac.getBean(MemberRepository.class) 가 들어간다고 생각
    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);
    }

    // 테스트 용도
    public MemberRepository getMemberRepository() {
        return memberRepository;
    }
}

 

기존과 동일하게 작용하는 것을 확인했다. 

public class AutoAppConfigTest {

    @Test
    void basicScan() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);

        MemberService bean = ac.getBean(MemberService.class);
        Assertions.assertThat(bean).isInstanceOf(MemberService.class);
    }
}

1. @ComponentScan

 

이때 스프링 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자만 소문자를 사용한다. 

2. @Autowired 자동 주입

 

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

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

생성자에 파라미터가 많아도 알아서 잘 등록을 해준다. 

탐색 위치와 기본 스캔 대상

 

이 패키지를 포함한 하위 패키지를 탐색하도록 설정 가능하다. 

@ComponentScan(basePackages = "")

basePackageClass = '" 도 가능하다. (클래스 지정)

 

지정하지 않으면 얘가 있는 패키지 하위로 탐색한다. 

package hello.springBasic;
@ComponentScan(
        basePackages = "hello.springBasic.member",
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)

 

김영한 님이 권장하는 방법

- 설정 정보 클래스의 위치를 최상단에 두고 basePackages 지정을 생략한다. 

참고로 스프링 부트를 사용하면 스프링 부트의 대표 시작 정보인 @SpringBootApplication을 이 프로젝트 시작 루트 위치에 두는 것이 관례이고 이 설정 안에 바로 @ComponentScan이 들어있다. 

 

컴포넌트 스캔 기본 대상

@Component 뿐아니라 @Controller(MVC 컨트롤러) @Service(비즈니스 로직) @Repository(데이터 접근 계층) @Configuration(스프링 설정 정보) 이들 또한 Component 스캔 대상이다. 

 

 

필터

 

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

BeanB는 exclude annotation에 포함되어 있어서 조회가 불가능하다. 

public class ComponentFilterAppConfigTest {

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


        org.junit.jupiter.api.Assertions.assertThrows(
                NoSuchBeanDefinitionException.class, () -> ac.getBean("beanB", BeanB.class)
        );
    }

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

    }
}

includeFilters와 excludeFilters를 사용할 일이 거의 없다. 

 

 

중복 등록과 충돌

 

자동 빈 등록 vs 자동 빈 등록

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

 

수동 빈 등록 vs 자동 빈 동록

- 수동 등록 빈이 우선권을 가진다.

 

Overriding bean definition for bean 'memoryMemberRepository' with a different definition: replacing [Generic bean: class [hello.springBasic.member.MemoryMemberRepository]

(수동 빈이 오버라이딩 해줌)

=> 이런 결과는 개발자 의도대로 일어나는 것이 아닌 실수로 일어나는 것이 많아 해결이 어려우니 최근 스프링 부트는 이런 상황에도 에러가 발생하도록 하였다. 

 

 

혼자 개발하는 것이 아니므로 명확하게 사용하는 것이 좋다. 

 

반응형