<aside> ❗

컴포넌트 스캔

</aside>

<aside> ❗

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

package hello.core;

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

@Configuration
// @Component 애너테이션이 붙은 클래스를 찾아서 자동으로 스프링 빈으로 등록
@ComponentScan(
        // @Configuration 내부에 @Component 애너테이션이 들어있다. 그러므로 컴포넌트 스캔시 스프링 빈으로 자동 등록됨
        // 컴포넌트 스캔으로 검색하는데 스프링 빈으로 등록하지 않을 것을 설정 (여기서는 @Configuration이 붙은 클래스를 제외)
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
        // -> Configuration 애너테이션이 붙은 클래스를 제외하겠다는 필터

)
public class AutoAppConfig {
    

}

[참고]



[참고]



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


package hello.core.scan;

import hello.core.AutoAppConfig;
import hello.core.member.MemberService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import static org.assertj.core.api.Assertions.*;

public class AutoAppConfigTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);

    @Test
    @DisplayName("모든 빈 출력하기")
    void findAllBean() {
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for(String beanDefinitionName : beanDefinitionNames){
            Object bean = ac.getBean(beanDefinitionName);
            System.out.println("bean = " + bean);
        }
    }

    @Test
    @DisplayName("내가 등록한 빈 출력하기")
    void findByMyApplication() {
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for(String beanDefinitionName : beanDefinitionNames){
            Object bean = ac.getBean(beanDefinitionName);
            BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
            if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION){
                System.out.println("bean = " + bean);
            }
        }
    }

    @Test
    void basicScan() {
        MemberService memberService = ac.getBean(MemberService.class);
        assertThat(memberService).isInstanceOf(MemberService.class);
    }
}

<aside> ❗

1. @ComponentScan

image.png

2. @Autowired 의존관계 자동 주입

image.png

image.png

<aside> ❗

컴포넌트 탐색 위치와 기본 스캔 대상

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

모든 자바 클래스를 다 컴포넌트 스캔하면 시간이 오래걸린다. 그래서 꼭 필요한 위치부터 탐색하도록 시작 위치를 지정할 수 있다.

@Component(
  // hello.core 부터 탐색 시작
	basePackages = "hello.core",
)

권장하는 방법

<aside> ❗

컴포넌트 스캔 기본 대상

@Component 뿐만 아니라 다음과 내용도 추가로 대상에 포함됨

@Component - 컴포넌트 스캔에서 사용

@Controller - 스프링 MVC 컨트롤러에서 사용

@Service - 스프링 비즈니스 로직에서 사용

@Repository - 스프링 데이터 접근 계층에서 사용

@Configuration - 스프링 설정 정보에서 사용

컴포넌트 스캔 뿐만 아니라, 다음 애너테이션이 있으면 스프링은 부가 기능을 수행

애너테이션은 메타 정보, 이 정보를 보고 부가 기능 수행

[참고]

<aside> ❗

필터

includeFilter - 컴포넌트 스캔 대상을 추가로 지정

excludeFilter - 컴포넌트 스캔에서 제외할 대상 지정

// 해당 애너테이션이 붙은 클래스는 스프링 빈으로 등록하기 위해 생성 
package hello.core.scan.filter;

import java.lang.annotation.*;

// Target - 어디에 붙는지 지정할 때 사용, Type이라고 하면 클래스에 붙는 것
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}
//해당 애너테이션이 붙은 클래스는 스프링 빈으로 등록 못하도록 하기 위해 생성
package hello.core.scan.filter;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {
}
// 스프링 빈에 등록할 것 
package hello.core.scan.filter;

@MyIncludeComponent
public class BeanA {
}
// 스프링 빈에 등록하지 않을 것
package hello.core.scan.filter;

@MyExcludeComponent
public class BeanB {
}
// 애너테이션에 따라 스프링 빈이 등록되는지 안 되는지 테스트
package hello.core.scan.filter;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.context.annotation.ComponentScan.*;

public class ComponentFilterAppConfigTest {

    @Test
    void filterScan(){
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(FilterConfig.class);
        assertThat(ac.getBean("beanA", BeanA.class)).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 FilterConfig {

    }
}

FilterType 옵션

ANNOTAION - 기본값, 애너테이션을 인식해서 동작 (생략 가능)

ASSIGNABLE_TYPE - 지정한 타입과 자식 타입을 인식해서 동작 (클래스 직접 선택 가능)

ASPECTJ - AspectJ 패턴 사용 (패턴으로 찾아오는 것)

REGEX - 정규 표현식 (정규 표현식 사용해서 가져오는 것

CUSTOM - TypeFilter 이라는 인터페이스를 구현해서 처리 (조건을 직접 프로그래밍해서 사용)

@Component
  includeFilters = 
       @Filter(FilterType.ANNOTATION, classes = MyIncludeComponent.class)
  
  excludeFilters = {
			 @Filter(FilterType.ANNOTATION, classes = MyExcludeComponent.class),
			 @Filter(FilterType.ASSIGNABLE, classes = BeanA.class)
  }

[참고]

<aside> ❗

중복 등록과 충돌

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

다음 두 가지 상황이 있다.

  1. 자동 빈 등록 VS 자동 빈 등록
  2. 수동 빈 등록 VS 자동 빈 등록

자동 빈 등록 VS 자동 빈 등록

수동 빈 등록 VS 자동 빈 등록

** 수동 빈 등록시 남는 로그

Overriding bean definition for bean 'memoryMemberRepository' with a different
definition: replacing

** 수동 빈 등록, 자동 빈 등록 오류시 스프링 부트 에러

Consider renaming one of the beans or enabling overriding by setting
spring.main.allow-bean-definition-overriding=true`

**

개발에서 명확하지 않은 것은 하면 안 된다. 개발은 혼자하는 것이 아니기 때문에 애매한 상황을 만들지 않는게 좋다. 어설픈 우선순위가 있을 때, 잡기 힘든 버그가 된다. 코드를 조금 더 쓰더라도 명확한 것을 항상 선택하는 것이 베스트이다.

</aside>