[Spring] 컴포넌트 스캔

imcool2551·2022년 1월 26일
0

Spring

목록 보기
4/15
post-thumbnail

본 글은 인프런 김영한님의 스프링 완전 정복 로드맵을 기반으로 정리했습니다.

컴포넌트 스캔


현재까지는 @Bean 애노테이션으로 빈 객체를 수동으로 등록했다. 실제 프로젝트에서는 빈으로 등록되어야 할 객체가 많기 때문에 이를 일일히 등록하다가 누락하는 등의 문제가 발생할 수 있다. 설정파일 길이도 길어지고 반복작업도 심해진다. 이때 수동 등록 대신 사용할 수 있는것이 컴포넌트 스캔이라는 빈 자동 등록 방법이다.

@ComponentScan, @Component


@Configuration
@ComponentScan
public class AutoAppConfig {
}
@Component
public class CreateOrderServiceImpl {
}
@Component
public class MemberRepositoryImpl {
}

설정정보 클래스에 @ComponentScan 애노테이션을 붙이면 @Bean 애노테이션이 붙은 메서드를 정의해주지 않아도 자동으로 스프링이 @Component 애노테이션이 붙은 클래스의 객체들을 빈으로 등록해준다.

수동 등록에선 메서드명을 빈의 이름으로 사용했다. 컴포넌트 스캔 방식은 클래스 이름의 앞글자를 소문자로 바꿔서 사용한다.

빈 이름빈 객체
createOrderServiceImplCreateOrderServiceImpl@x01
memberRepositoryImplMemberRepositoryImpl@x02

@Bean("customName") 처럼 기본값 대신 사용할 빈의 이름을 지정할 수 있는 것처럼 @Component("customName") 으로 빈의 이름을 직접 지정해줄 수 있다.

수동으로 빈을 등록할때는 아래처럼 의존관계를 주입해주면 된다.

public class AppConfig {

    @Bean
    public CreateOrderService createOrderService() {
        return new CreateOrderServiceImpl(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new MemberRepositoryImpl();
    }
}

컴포넌트 스캔으로 등록된 빈은 @Autowired 애노테이션을 통해서 필요한 의존 객체들을 자동 주입받는다. 의존관계 자동 주입에 대해선 다음 글에서 다루도록 하겠다.

@ComponentScan의 범위


@ComponentScan 애노테이션을 붙였다고 해서 프로젝트의 모든 클래스를 컴포넌트 스캔 하지는 않는다. 그러면 쓸데없이 외부 라이브러리까지 탐색해야 되므로 비효율적이다. 애노테이션에 아무런 옵션도 주지 않으면 해당 클래스가 정의된 패키지부터 그 하위 패키지까지 컴포넌트 스캔한다.

@Configuration
@ComponentScan(
        basePackages = "hello.service"
)
public class AutoAppConfig {
}

옵션을 주면 해당 패키지부터 하위 패키지까지 모두 스캔한다. basePackages = {"hello.repository", "hello.service"} 처럼 배열 형식으로 시작 위치를 여러개 지정할 수 있다.

컴포넌트 스캔 설정 정보는 프로젝트의 핵심적인 정보이기 때문에 프로젝트 최상단에 두고 아무런 옵션도 주지 않는 것이 이상적이다. 스프링 부트도 이 방법을 사용한다.

스프링 부트 프로젝트엔 프로젝트 최상단에 스프링 애플리케이션을 시작하는 메인 메서드가 있는 클래스가 있다. 이 클래스는 @SpringBootApplication 애노테이션이 있는데 이 애노테이션은 @ComponentScan 애노테이션을 포함하고 있다. 참고로, 자바 애노테이션에는 상속 관계가 없지만 스프링에선 애노테이션이 들고 있는 애노테이션을 인식하는 기능을 지원한다.

필터- 스캔 대상에서 제외하기


컴포넌트 스캔에서 특정 대상을 제외하고 싶은 특수한 경우가 있다. 이 때 사용하는 것이 @ComponentScan의 excludeFilters와 @Filter 애노테이션이다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyExcludeComponent {
}
@Configuration
@ComponentScan(
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,
                classes = MyExcludeComponent.class)
)
public class AppConfigWithFilter {
}

type 속성에 FilterType.ANNOTATION, classes 속성에 필터로 사용할 애노테이션 타입을 줬다. @MyExcludeComponent 애노테이션이 붙은 클래스는 컴포넌트 스캔 대상에서 제외될 것이다. 여러 값을 지정하고 싶으면 classes 속성을 배열 형태로 쓸 수 있다.

FilterType 에는 ANNOTATION을 포함해서 5가지 옵션을 제공한다.

  • ANNOTATION: 애노테이션을 지정 (기본값)
  • ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 지정
  • ASPECTJ: AspectJ 패턴을 사용해서 지정
  • REGEX: 정규 표헌식을 사용해서 지정
  • CUSTOM: TypeFilter 인터페이스를 구현해서 처리

컴포넌트 스캔 대상


다음 애노테이션을 붙인 클래스는 컴포넌트 스캔 대상이 된다.

  • @Component: 컴포넌트 스캔 대상으로 지정된다.
  • @Controller: 스프링 MVC 패턴에서 Controller에 해당된다.
  • @Service: 스프링 비즈니스 로직 처리에 해당된다.
  • @Repository: 스프링 데이터 접근 계층에 해당된다. 데이터베이스 예외를 스프링 예외로 변환해준다.
  • @Aspect: 스프링 AOP 처리에 해당된다.
  • @Configuration: 스프링 설정 정보에 해당된다.

컴포넌트 스캔 충돌


1. 이름 충돌

타입이 다르고 이름만 같은 클래스가 모두 컴포넌트 스캔의 대상이 될 수 있다. 이 상태로 스프링 애플리케이션을 시작하면 ConflictingBeanDefinitionException이 터진다. 이 문제는 @Component 애노테이션을 통해 이름을 바꿔주면 해결할 수 있다.

2. 같은 타입 충돌

@Component
public class MemberRepository {

}
@Configuration
public class AppConfig {

    @Bean
    public MemberRepository memberRepository() {
        return new MemberRepository();
    }
}

수동 등록한 빈과 컴포넌트 스캔이 된 빈이 충돌하는 경우가 있다. 이 경우 수동 등록한 빈이 컴포넌트 스캔이 된 빈을 덮어쓴다. 즉, MemberRepository 타입의 빈을 두 개 등록했지만 최종적으로 컨테이너에 한 개만 존재하게 된다. 그러나, 이렇게 컴포넌트 스캔이 된 빈이 덮어씌워져서 사라지는 것은 의도하지 않은 경우가 대부분이며 잡기 어려운 버그의 원인이 될 수 있다.

그래서 스프링 부트 최신 버전에서는 수동 등록과 컴포넌트 스캔이 충돌할 경우 애플리케이션 로딩에 오류를 발생시키도록 기본값을 바꿨다. application.properties 파일에 spring.main.allow-bean-definition-overriding=true 옵션을 줘서 기본값을 덮어쓰는 행위를 허용할 수 있지만 권장되는 방법은 아니다.

@Configuration
public class AppConfig {

    @Bean
    public MemberRepository memberRepository2() {
        return new MemberRepository();
    }
}

위와 같이 기본이름 대신 다른 이름을 지정해주면 같은 타입의 빈이 충돌 없이 두 개 존재하게 된다. 이 경우 MemberRepository 타입만으로는 빈을 구별할 수 없으므로 @Qualifier 애노테이션으로 알맞은 빈을 지정해줘야 한다. @Qualifier 애노테이션에 관해선 의존관계 자동주입에서 다루도록 하겠다.

profile
아임쿨

0개의 댓글