당니의 개발자 스토리

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

스프링/스프링 핵심 원리 - 기본편

탐색 위치와 기본 스캔 대상

clainy 2024. 1. 24. 16:00

탐색 위치와 기본 스캔 대상

이번 시간에는 컴포넌트 스캔의 탐색 위치기본 스캔 대상에 대해서 알아보겠습니다.

먼저, 탐색할 패키지의 시작 위치를 지정을 할 수가 있어요.

우리가 @ComponentScan 이라고 적었잖아요. 그 밑에 basePackages 라는 걸 적을 수가 있습니다.

우리 같은 경우에는 "hello.core"를 적을 수 있고, 이렇게 적으면 이 위치에서부터 시작해서 이 패키지를 포함한 하위 패키지를 모두 찾아들어가는 겁니다.

만약에 "hello.core.member" 로 바꾸면,

이 패키지부터 하위 패키지를 찾아가는 거예요. 그래서 이렇게 해버리면 member만 컴포넌트 스캔의 대상이 됩니다. 한번 돌려볼까요?

기존에 돌렸던 basicScan 테스트를 돌리면,

memberServiceImpl이랑 memoryMemberRepository, 2개만 등록되어있습니다. 나머지 rateDiscountPolicy나 orderServiceImpl은 스프링 빈으로 등록이 안됐죠.

basePackages사용하면 어디서부터 찾는지 지정할 수 있습니다.

그래서 basePackages탐색할 패키지의 시작 위치를 지정합니다. 왜 지정하냐면, 이게 없으면 모든 Java 코드를 다 뒤져요. 엄청 오래 걸린단 말이에요. 심지어 java만 뒤지겠어요? 이 라이브러리까지 다 뒤진단 말이에요.

엄청 오래 걸리겠죠. 그래서 여러 라이브러리가 섞여 있을 때 컴포넌트 스캔은 안 하고 싶을 때도 있잖아요. '얘들만 할 거야' 이럴 때도 유용하게 쓸 수 있습니다.

그래서 basePackages로 탐색할 패키지의 시작 위치를 지정하고요. 이 패키지를 포함해서 하위 패키지를 모두 탐색합니다.

그리고 basePackages = {"hello.core", "hello.service"} 이렇게 여러 개 둘 수도 있어요. 이렇게 하면, 이 두 개의 패키지를 쭉 하위를 뒤집니다.

그리고 basePackageClasses 라는 걸 지정할 수도 있어요. 지정한 클래스의 패키지를 탐색 위치로 지정하는 거예요.

예를 들어서, basePackageClasses를 AutoAppConfig.class로 지정하면,

AutoAppConfig의 패키지는

hello.core 패키지에 있잖아요. 그러면, hello.core 패키지에서부터 찾는 겁니다. 그럼 아예 지정을 안하면 어떻게 되나요?

이게 중요합니다. 지정하지 않았을 경우의 디폴트가 되게 중요해요. 의외로 웬만한 관례로 코딩하는 걸로 점점 그렇게 하고 있는 시대로 바뀌고 있어서, 옛날엔 다 하나하나 지정했는데 요즘에는 웬만하면 편하게 가는게 좋습니다.

지정하지 않으면, @ComponentScan을 붙인 클래스 있죠?

AutoAppConfig가 포함된 hello.core 패키지부터 시작해서, hello.core랑 hello.core의 하위 패키지를 다 뒤집니다.

그러면 우리 프로젝트는 다 뒤지겠죠.

따라서, 지정하지 않으면 @ComponentScan이 붙은 설정정보 클래스의 패키지가 시작 위치가 됩니다.

저는 관례를 사용하는 것을 권장 드려요.

개인적으로 즐겨 사용하는 방법은 패키지 위치를 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 거예요. 최근 스프링 부트도 이 방법을 기본으로 제공합니다. 무슨 말인지는 조금 이따 설명을 드릴게요.

예를 들어서 프로젝트가 이런 구조로 되어 있어요.

com.hello

com.hello.serivce

com.hello.repository

이런 경우에 나의 프로젝트 최상단은 com.hello란 말이에요.

여기에 AutoAppConfig 같은 메인 설정 정보를 두는 거예요.

그리고 AutoAppConfig에 @ComponentScan 애노테이션을 붙이고, basePackages 지정은 생략하는 겁니다.

그럼 com.hello부터 시작해서 com.hello.serivce, com.hello.repository 두 개도 다 뒤지겠죠. 내 프로젝트와 관련된 건 자동으로 다 뒤지게 된단 말이에요.

이렇게 하면 com.hello를 포함한 하위는 모두 자동으로 컴포넌트 스캔의 대상이 됩니다. 그리고 프로젝트 메인 설정 정보프로젝트를 대표하는 정보란 말이에요. 왜냐면 프로젝트를 딱 깠을 때 구성 정보를 먼저 봐야 될 거 아니에요. 그래서 AppConfig도 그렇고, AutoAppConfig도 그렇고 프로젝트를 대표하는 정보기 때문에, 프로젝트 시작 루트 위치에 두는 게 좋다고 생각을 해요.

참고로 스프링 부트를 사용하면 스프링 부트의 대표 시작 정보@SpringBootApplication를 이 프로젝트 시작 루트 위치에 두는 것이 관례예요. 왜냐면 이 설정안에 바로 @ComponentScan이 들어있어서 그래요. 한 번 볼게요.

처음에 프로젝트를 자동으로 만들면,

CoreApplication이 자동으로 만들어져요. 스프링 부트를 돌리는 애플리케이션, 클래스죠.

@SpringBootApplication를 까보면,

스프링 부트는 기본적으로 다 컴포넌트 스캔으로 돌아가거든요. 그래서 @ComponentScan이 붙어있어요.

무슨 말이냐면, 스프링 부트를 쓰면 내 프로젝트를 여기, hello.core에서부터 그냥 다 컴포넌트 스캔 하겠다는 거에요.

그래서 스프링 부트를 쓰면, 이 @ComponentScan에 의해서 자동으로 다 스프링 빈으로 등록되고 하는 거에요. 스프링 입문에서 설정 정보를 따로 안 만들었는데,

클래스에다가 @Component를 해놓으면 자동으로 다 스프링 빈으로 등록됐던 이유가 여기 있습니다.

그래서 스프링 부트를 쓰면 사실 @ComponentScan 자체를 쓸 필요가 없어요. 왜냐하면 부트에서 이미 다 해주니까요.

그래서 관례를 따라서 프로젝트 루트에다가 @ComponentScan을 지정을 하고, 불필요한 거 몇 개 정도만 Exclude 해서 빼서 쓰는 걸 권장 드립니다.

최근에 스프링 부트를 쓰니까 부트에서 다 알아서 컴포넌트 스캔이 되겠죠.


이제 컴포넌트 스캔의 기본 대상에 대해서 설명을 드릴게요.

컴포넌트 스캔은 @Component 뿐만 아니라 다음과 내용도 추가로 컴포넌트 스캔 대상에 포함합니다.

@Controller스프링 MVC 컨트롤러에서 주로 사용하는 애노테이션이고, @Service스프링인데, 핵심 비즈니스 로직에서 사용하고, @Repository는 jpa나 jdbc 등등 데이터에 접근하는 계층에서 주로 많이 사용하는 애노테이션이에요.

그런데 얘네들을 붙여놓으면 자동으로 컴포넌트 스캔의 대상이 된대요.

컨트롤러는 현재 MVC가 없어서 안될거고, @Service 가볼게요.

org.springframework.stereotype 이거든요.

그리고 @interface 라고 되어있으면, Service애노테이션이라는 건데, @Component가 붙어있죠.

@Repositroy에도 @Component가 붙어있습니다.

@Controller는 라이브러리를 받아야해서, 지금은 못 엽니다.

그리고 @Configuration에도 @Component가 붙어있습니다.

그래서 @Component, @Controller, @Service, @Repository, @Configuration이 붙어있으면 컴포넌트 스캔에 의해서 다 스프링 빈으로 등록이 돼버립니다.

그런데 참고로 중요한 게 하나 있어요.

설명할 때, @Service@Component가 붙어있으니까 등록된다고 했는데요.

사실 annotation에는 상속관계라는 게 없어요. 그리고 내가 애노테이션에다가 애노테이션을 붙였다고 해서 연동되거나 하는 기능이 전혀 없어요. 그래서 이렇게 애노테이션이 특정 애노테이션을 들고 있는 것을 인식할 수 있는 것은 Java 언어가 지원하는 기능은 아니고, 스프링이 지원하는 기능입니다.

스프링은 애노테이션을 만들어서 애노테이션에 붙이거나 하는 이런 것들을 굉장히 유연하게 지원하거든요.

그런데 아셔야 되는 게 Java 언어가 지원하는 기능이 아니라, 스프링이 지원하는 기능이다라는 걸 알아두시면 됩니다.

나중에 뒤에서 Custom 애노테이션을 하나 만드는 거를 강의에서 해볼게요.

참고로 컴포넌트 스캔의 용도 뿐만 아니라, 이 애노테이션들이 있으면 스프링은 부가 기능을 수행합니다.

원래 애노테이션이 메타 정보 거든요. 그래서 이걸 보고 '아 내가 뭘 더 해야지' 이렇게 할 수가 있어요.

그래서 @Controller가 있으면 컴포넌트 스캔의 대상도 되지만, 스프링 MVC 컨트롤러로 인식합니다.

Spring MVC가 '아 이건 내가 사용하는 컨트롤러야' 라고 인식하고 그 메커니즘에 맞춰서 동작합니다.

그리고 @Repository 스프링 데이터 접근 계층으로 인식을 하면서, 데이터 계층의 예외를 스프링에 추상화된 예외로 바꿔줘요. 예를 들어서, 특정 DB나 예외가 터져요. 그러면 이게 서비스 계층까지 올라온단 말이에요. 그러면 나중에 DB를 바꾸거나 했을 때 예외 자체가 바껴버리거든요. 그럼 되게 곤란해져요.

예를 들어서 리포지토리 계층의 A라는 DB를 쓰다가 갑자기 다른 DB를 바꿨어요. 그러면 그 DB에 맞춰서 또 다른 예외가 올라와 버리면 그것 때문에 서비스 계층이나 다른 계층의 코드도 흔들리거든요.

그래서 스프링이 그렇게 안 되도록 스프링의 예외를 추상화해서 반환을 해줘요.

잘 이해가 안되시면 나중에 스프링 데이터 접근 관련 강의를 찍을 건데, 그 강의를 들어주시면 될 것 같습니다.

아무튼 @RepositoryDB에 접근하는 예외를 스프링 예외로 변환해주는 역할도 해줍니다.

그리고 @Configuration은 앞서 보았듯이 스프링 설정 정보로 인식하고 스프링 빈이 싱글톤을 유지하도록 추가적인 처리(ex. CGLIB)를 해줍니다.

그리고 @Service는 의외로 특별한 처리를 하지 않아요. 대신에 이거는 개발자들이 '아 얘는 우리 어떤 중요한 서비스 로직이 들어가는구나, 얘가 비즈니스 계층이구나' 하고 개발자들이 인지하는데 도움이 됩니다.

그래서 보통 @Service 계층에서 트랜잭션 애노테이션을 걸어서 트랜잭션도 시작하죠. 왜냐면 비즈니스 로직이 시작할 때, 트랜잭션이 걸려있는 게 맞으니까요.

여기까지 정리를 해봤구요.

참고로 useDefaultFilters 라는 옵션이 기본으로 켜져있는데, 이 옵션을 끄면 기본 스캔 대상들이 제외가 돼요. 참고로 이런 옵션도 있다는 정도만 알고 넘어가시면 됩니다.


다음 시간에는 필터에 대해서 알아보겠습니다.