백앤드(스프링)

컴포넌트 스캔

유승혁 2022. 1. 20. 01:32

 이번 글은 꽤 짧을 것 같다. 왜냐면 내가 짧게 쓸 꺼니깐 ㅎㅎ. 컴포넌트 스캔이 저번에 배운 자바 빈을 다르게 설정하는 거기 때문에 개념위주 보단 코딩 시 유의점 같은 부가적인 이야기를 할 것 같다. 

 

1. 컴포넌트 스캔

 저번에 배운 스프링 빈으로 일일이 등록하기란 생각보다 귀찮을 수 있다. 그래서 한번에 쫘악 긁어 모아 스프링 빈으로서 스프링 컨테이너에 등록하는 방법인 컴포넌트 스캔이 있다.

 그리고 다음 글에서 엄청엄청 자세히 다룰 의존관계 자동주입(@Autowired)에 대해 배울 거지만 여기서 소개해야 겠다. 말 그대로 기존에는 생성자 주입을 통해 의존관계를 설정 했다면 이젠 그딴 거 필요 없다. 바로 위 어노테이션이 알아서 해준다.. 밑에서 말하겠당!

 

2. In Code

@Configuration
@ComponentScan (
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
    @Bean(name = "memoryMemberRepository")
    MemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }
}

 저게 끝이다. 저번에는 AppConfig클래스에 @Configuration 어노태이션 붙이고 생성자 함수마다 @Bean붙이는 수고를 했지만 이젠,,!! @ComponentScan 을 넣어주면 된다. 대박이죠잉?

 일단 excludeFilters 설명은 제하고 @ComponentScan 옆에 괄호 치고 설정 정보들을 넣는 것이다. 일단 아무 설정 정보가 없다고 하면,

현재 프로젝트 모든 파일들 다 열어서 @Component 어노테이션이 붙어있는 클래스들을 찾는다(이 클래스는 밑에 코드에 있다). 엄쩡 오래 걸뤼겠쮜? 하지만 스프링이 디폴트로 그냥 우리가 선언한 AutoAppConfig 위에 보면 이 녀석이 속해있는 패키지가 보일 것인데 그 패키지 아래에 있는 것들만 찾아본다. (고맙다 스프링아!)

 

2-2. Codes which will be registered in Spring Container

 왜 영어로 쓰냐고? 오늘 힘든 알고리즘 문제도 있었고 시간이 늦어서 너무 졸리다..(00:51) 나 원래는 12시 전에 자는데,, 오늘 알고리즘 문제가 발목 잡아서 제정신 아님,, 근데 아직도 못풀어서 너무 슬프다.. (내일하면 되는데, 성격상 그 날 할일은 꼭 해야해서,,ㅎ)

 아무튼 코드 보여주시죠~! 이 코드는 위에서 말한, 이제 스프링 빈으로서 등록될 클래스라고 명시하기 위한 설정을 넣은 코드다.

@Component
public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository;

     @Autowired
    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
 }

 위에서 보이듯이 @Component 어노태이션을 붙였다. 저렇게 하면 @ComponentScan이 요 클래스를 인식 하여 스프링 빈으로 등록한다. 이때 신기한 것은 스프링 컨테이너에 기존에는(@Bean 방식) 그냥 클래스 명 그대로 등록되었는데 이젠 memberServiceImpl 처럼 맨 앞글자는 무적권 소문자로 나타낸다. 이상하지? ㅋㅋ

 And um,, @AutoWired 어노테이션은 지금 보면 MemberServiceImpl는 memberRepository를 의존하고 있기에 의존관계를 주입하라고 명시되어 있다고 생각하면 된다. 마치 기존에 ac.getBean(MemberRepository.class) 로 가져오는 것과 같은 이치.물론! memberRepository는 스프링 컨테이너에 등록이 되어 있어야 한다!!! 

 물론 MemberRepository는 인터페이스이다. 그러하니 이의 자식인 예를 들어 memoryMemberRepository가 등록 되어 있다면 그 녀석을 반환한다. 아니 잠시만, 만약 자식 여러개가 다 스프링 컨테이너에 등록 되어있다면 무엇을 찾아? 오류 안나? 이건 잠시후에,,ㅎㅎ

 아무튼 이런 방식으로 스프링 컨테이너에 등록 되었다고 컴파일러가 보여준다. 아래 사진 처럼,,

등록!

3. @ComponentScan의 옵션들

 첫번째 코드를 보면 excludeFilters 가 있다. 이는 말 그대로 컴포넌트 스캔 시 포함하지 않고 싶어할 클래스 들이다. FilterType을 뒤에 ANNOTATION 이라고 붙어있는데 말 그대로 classes= 에 명시되어 있는 어노테이션들은 빼고 컴포넌트 스캔에 등록 한다는 뜻이다.

 만약 어노테이션 대신 ASSIGNABLE_TYPE 이라고 클래스(또는 인터페이스) 즉, 타입 명이 와서 그 타입과 그 자식들을 인식한다. CUSTOM으로 하면 내가 만든 TypeFilter로 인식한다.

 다른 옵션인 includeFilters 는 exclude와 반대로 이 녀석들은 반드시 포함 한다는 것이다. FilterType을 위에서 설명한 것처럼 사용해서 클래스를 기준으로 내가 만든 필터를 기준으로 반드시 포함 시키게 할 수도 있다. 이 두 개를 필터라고 부르기도 한다.

 또 다르게 자주 사용하는 옵션인 basePackages가 있는데 탐색할 패키지의 시작 위치를 적어 놓으면 그 하위 패키지를 모두 탐색한다.

basePackages = {"A.core", "B.Service"} 처럼 여러 개를 지정 할 수 있다. basePackageClasses도 있는데 닉값대로 class를 기준으로 탐색 하는 것이다.

 마지막으로 이렇게 설정 정보들을 연결 할 때는 ,를 사용 해서 연결한다... 절대 ; 땀 흘리는 거 붙이면 안돼! 아래 코드로 보여주겠당.

@ComponentScan (
        basePackages = "hihi.core.Member",
        basePackageClasses = AutoAppConfig.class,
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)

 

4. 컴포넌트 스캔 기본 대상

 @ComponentScan 어노테이션이 항상 @Component만 인식하는 것은 아니다. @Controller, @Service, @Repository, @Configuration 어노테이션 또한 인식 한다. 사실 이들에게 @Component가 묶여있다. 얘네 코드 까보면 public @interface Controller{} 로 클래스 가 선언 되어 있으면 그 위에 @Component가 명시되어있다. 자바는 이 두 어노테이션을 묶어주지 않지만 스프링이 이들을 묶어주어 Controller를 Component로서 인식을 하게 만들어준다.. 다시 한번 고맙다 스프링아!

 각 어노테이션에 대한 이야기는 차차 다루기도 할 것이지만 대략 이야기 하자면 @Contoller는 스프링 MVC에서, @Repository는 스프링 데이터 접근 계층으로 인식, @Configuration 은 앞에서도 이야기했지만 스프링 설정정보로서, @Service는 여기에 비즈니스 로직을 적을 것이구나! 라는 식으로 인식한다.

 

5. 중복 등록과 충돌

 앞에서 이야기 했듯이, MemberRepository라는 인터페이스에 여러 자식들이 스프링 빈으로 등록되어서 memberRepository를 정작 사용 하게 될때 어느 자식을 고르느냐! 라고 물었다. 사실 그렇게 되면 아래 사진과 같은 메시지가 나오면서 형씨 안돼요! 중복 됐다고요! 당장 자식 클래스 하나에만 @Component붙이세요! 메시지가 나온다.

충돌!충돌!

 뿐만 아니라 @Component(name="이름") 과 같은 방식으로 등록될 스프링 빈의 이름을 설정해주는데 서로 다른 빈이 같은 이름으로 등록될 때도 위 같은 메시지가 나온다. 사실 이런 경우는 자동으로 등록되는 두 빈 끼리 충돌이 일어났다.

 만약 수동 빈 등록(@Bean)과 자동 등록이 충돌이 일어난다면? 수동이 이겨버린다. 사실 당연하겠지? 수동이 수고를 더 들였는데,,,

 그렇게 우리는 무심코 지나가게 되고,, 이것은 큰 문제로 퍼져나가,, 막대한 시간과 돈을 들이게 되는데.. 

 이럴까봐 우리에게 스프링 부트는 수동 빈과 자동 빈의 충돌 시 바로 에러 메시지가 나온다. 아래 사진들처럼,,(너도 고맙다 스프링 부트야!!)

 설명도 정갈하다. 충돌이 일어난 memoryMemberRepository 가 자바 빈으로 등록 되지 않는다고 한다.

 그런데 만약에, 혹시나, 추천하지 않지만, 사실 일부로 충돌 시켰고 수동 빈으로 등록 하고자 한다면 첫번 째 사진 밑에 나와있는 spring.main.allow-bean-definition-overrideing = true 를 main > resources > application.properties에 적어주면 문제 없이 실행된다. 하지만 정말정말 비추한다..

참고로 만약에 등록 안된 스프링 빈을 부르고자 한다면 아래 사진과 같은 메시지가 나온다.

그런거 없어요 ㅠㅜ

 

짧게 쓴다고 했지만 벌써 1시 반이네,, 빨리 자야겠다. 다음 글은 의존관계 자동주입 (DI 자동주입) 에 대해 자세히 다룰 예정이다. 재밌겠지? 신나겠지? 암튼, 휴 요새 알고 공부를 하는데 뜻하지 않게 시간을 너무 잡아먹어서 글을 못쓰고 있다.. 주말 이용해서 올려야지,,, 근데 오늘 안 풀린 문제는 진짜 어렵다... 뭔가 알것 같아서 더 미치겠다 ㅋㅋㅋㅋㅋ 내일 다시 해봐야지! 포기란 없다.

'백앤드(스프링)' 카테고리의 다른 글

의존관계 자동주입 2편  (0) 2022.01.25
의존관계 자동 주입 1편  (0) 2022.01.22
싱글톤!  (2) 2022.01.18
스프링 컨테이너와 스프링 빈  (0) 2022.01.15
IOC, DI 그리고 컨테이너에 대하여,,,  (0) 2022.01.13