@ComponentScan

EUNJI LEE·2023년 11월 18일
0

Spring

목록 보기
11/13

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

스프링 빈을 등록할 때 자바 코드에 @Bean이나 XML의 <bean> 등을 이용하여 설정 정보에 직접 등록할 빈을 나열할 수 있지만 이렇게 사용하게 되면 실무에서 등록해야 할 스프링 빈이 수십, 수백개가 되는 경우 설정 정보가 커지고 너무 번거롭다는 문제가 발생한다. 또한, 스프링 빈으로 등록해야 할 것을 누락하는 경우 찾기 힘들어지기도 한다.

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

@ComponentScan

해당 애너테이션은 @Component이 붙은 모든 클래스를 스프링 빈으로 등록한다. 이 때 스프링 빈의 기본 이름은 클래스 명을 사용하되 맨 앞글자만 소문자로 사용한다.

ex) MemberService(클래스 이름) → memberService(스프링 빈 이름)

탐색 위치 지정

basePackages 속성을 지정해서 컴포넌트 스캔을 시작할 대상 위치를 지정할 수 있다. 지정하지 않으면 컴포넌트 스캔 애너테이션을 사용한 설정 정보 클래스 위치를 탐색 위치 대상으로 지정한다.

ex) @ComponentScan( basePackages=”hello.core.member”) : hello.core.member 패키지 하위에서만 컴포넌트 스캔을 실행한다. 상위 패키지는 컴포넌트 스캔을 적용하지 않는다.

💡 권장 방법은 패키지 위치를 따로 지정하지 않고 설정 정보 클래스 위치를 프로젝트 최상단에 두는 것이다. 설정 정보 클래스는 프로젝트를 대표하는 정보이기 때문에 시작 루트 위치에 지정해두는 것이 좋고 그렇게 하면 하위 경로가 전부 컴포넌트 대상이 되기 때문에 basePackages 속성을 생략할 수 있다.
스프링 부트를 사용하면 스프링 부트의 대표 시작 정보인 @SpringBootApplication이 있는 시작 설정 정보를 시작 위치에 두는 것을 관례로 하고, @SpringBootApplication 애너테이션에는 @ComponentScan이 포함되어있다.

기본 스캔 대상

@Component 애너테이션 외에도 다음과 같은 애너테이션들을 추가로 대상에 포함한다.

  • @Component : 컴포넌트 스캔
  • @Controller : 스프링 MVC 컨트롤러에서 사용, 스프링 MVC 컨트롤러로 인식
  • @Service : 스프링 비즈니스 로직에서 사용, 별다른 기능을 추가적으로 하지 않지만 개발자가 핵심 비즈니스 로직을 구분하는데 사용
  • @Repository : 스프링 데이터 접근 계층에서 사용, 스프링 데이터 접근 계층으로 인식하면서 데이터 계층의 예외를 스프링 예외로 변환
  • @Configuration : 스프링 설정 정보에서 사용, 스프링 설정 정보로 인식하고 싱글톤이 유지되도록 추가 처리

위와 같은 애너테이션들을 대상에 포함하는 이유는 해당 애너테이션들에 전부 @Component 애너테이션이 포함되어 있기 때문이다.

🚨 애노테이션에는 상속 관계가 존재하지 않는다. 때문에 애노테이션이 특정 애노테이션을 포함하고 있는 것을 인식하게 만들어주는 건 자바에서 지원하는 기능이 아닌 스프링에서 지원하는 기능이다.

useDefaultFilters

기본적으로 켜져 있는 옵션이지만 해당 옵션을 끄면 기본 스캔 대상들이 제외된다.

FilterType 옵션

ANNOTATION : 기본값, 애노테이션을 인식해서 동작한다.

ASSIGNABLE_TYPE : 지정한 타입과 자식 타입을 인식해서 동작한다.

ASPECTJ : AspectJ 패턴을 사용해서 동작한다.

REGEX : 정규 표현식을 사용해서 동작한다.

CUSTOM : TypeFilter라는 인터페이스를 구현해서 처리한다.

@Component

스프링은 @Component 애너테이션을 사용한 클래스들을 전부 스프링 빈으로 등록해준다. @Configuration 애너테이션도 @Component 애너테이션을 포함하고 있기 때문에 컴포넌트 스캔의 대상이 된다.

@Component를 사용해서 스프링 빈으로 등록할 때 이름을 따로 지정해서 사용할 수 있다. @Component(”memberService2”)처럼 사용하면 해당 클래스의 빈 이름은 memberService2가 된다.

@Autowired

생성자에 해당 애너테이션을 지정하면 스프링 컨테이너가 자동으로 해당하는 스프링 빈을 찾아서 주입한다. 자동 의존관계 주입이 되는 것이다. 이 때, 기본 조회 전략은 타입이 같은 빈을 찾아서 주입하게 된다. getBean(MemberService.class)와 동일하다고 이해하면 된다.

💡 생성자에 파라미터가 많다고 하더라도 상관없이 전부 찾아서 의존관계를 주입해준다.

ClassPathBeanDefinitionScanner

프로젝트를 실행시키면서 찍힌 로그를 보면 위와 같은 스캐너가 동작한 것을 볼 수 있는데 컴포넌트 스캐너가 잘 동작했음을 알 수 있다.

중복 등록과 충돌

자동 빈 등록 vs 자동 빈 등록

컴포넌트 스캔에 의해서 자동으로 스프링 빈이 등록되는데 이름이 같은 경우, ConflictingBeanDefinitionException이 발생한다.

수동 빈 등록 vs 자동 빈 등록

수동으로 등록하는 빈의 이름과 자동으로 등록하는 빈의 기본 설정되는 이름이 동일한 경우 수동으로 등록하는 빈이 우선권을 가지게 된다. MemberService라는 클래스를 자동으로 빈으로 등록하면서 memberService라는 이름을 갖게 되고 수동으로 memberService라는 이름을 가진 빈을 등록하면 예외 없이 정상적으로 실행하게 되면서 로그에 Overriding bean definition for bean라는 메시지가 포함된 로그가 찍힌다.

이렇게 수동이 우선권을 갖도록 개발자가 의도했다면 우선권이 존재하는 것이 장점일 수 있지만 개발자가 의도하지 않은 경우 이렇게 우선권이 설정되면 개발자가 의도하지 않았는데 설정이 꼬이게 될 수 있다. 하지만 예외가 발생하지 않기 때문에 에러를 찾기도 힘들어지는 경우가 발생한다.

그래서 최근 스프링 부트에서는 수동 빈 등록과 자동 빈 등록이 충돌나면 오류가 발생하도록 기본 값이 변경됐다.

강의 : 스프링 핵심 원리 - 김영한 님

profile
천천히 기록해보는 비비로그

0개의 댓글