스프링 컨테이너에 빈을 등록하는 방식에는 수동과 자동 등록 방식이 있었습니다.
수동 등록은 설정 정보에서 관리해야하는 빈이 많아지면 여러가지 문제점을 동반하기 때문에 자동 등록, 컴포넌트 스캔 기능을 제공하고 있습니다.
인텔리제이 자동완성 중에 @ComponentScan
이 있고, @ComponentScans
라는 두 가지 어노테이션이 있어서 차이를 좀 검색해봤습니다.
결론적으로 말하면 @ComponentScans
는 사용할 일이 거의 없다입니다.
@ComponentScans
는 자바 8에서 등장한 Repeating Annotations이 등장하기 전에 반복 어노테이션을 사용하기 위한 방법입니다.
자바 8 버전 이전에는 아래 코드처럼 동일한 어노테이션을 반복해서 사용할 수 없었습니다.
@Configuration
@ComponentScan(basePackages = "")
@ComponentScan(basePackageClasses = BaseConfig.class)
class AppConfig () {...}
그래서 반복 어노테이션을 위해 @ComponentScans
를 이용해서 반복되는 어노테이션을 아래와 같이 묶었다고 합니다.
@Configuration
@ComponentScans({
@ComponentScan(basePackages = ""),
@ComponentScan(basePackageClasses = BaseConfig.class)
})
class AppConfig {}
따라서 Java 8 이상의 버전을 사용한다면, 신경을 쓸 내용은 아니라고 합니다. 자동 완성 import할 때 잘 보고 해야하는 것 정도만요.
@ComponentScan
은 스프링에서 자동으로 빈을 등록하기 위한 기능을 제공하는 어노테이션입니다.
@ComponentScan
은 스프링이 빈으로 관리하도록 명시하는 @Component
어노테이션이 붙은 클래스들을 찾아서 자동으로 스프링 컨테이너에 등록해서 관리합니다. 그래서 @ComponentScan
을 사용하면 @Bean
을 통해 일일이 수동 등록할 필요가 없어지게 됩니다.
@Repository, @Service, @Controller
와 같은 Stereotype 어노테이션이나@Configuration
어노테이션 내부에@Component
라는 어노테이션이 포함되어 있기 때문에@Repository, @Service, @Controller, @Configuration
도 컴포넌트 스캔의 대상에 포함됩니다.
그래서 직접 만든 빈을 컴포넌트 스캔 대상에 포함시키고 싶다면 클래스에 @Component
어노테이션을 붙여주면 직접 만든 빈도 컴포넌트 스캔에 의해 자동 등록되게 됩니다.
먼저 설정 정보를 담을 AppConfig.java
를 하나 만들어줍니다. 설정 정보를 나타내는 @Configuration
과 빈 자동 등록을 위한 @ComponentScan
어노테이션만을 작성합니다.
@Configuration
@ComponentScan
public class AppConfig {}
그 다음으로 Driver 클래스와 Vehicle 인터페이스, Vehicle을 구현한 Sedan 클래스를 작성합니다.
@Component
public class Driver {
private final Vehicle vehicle;
@Autowired
public Driver(Vehicle vehicle) {
this.vehicle = vehicle;
}
public void pressAccelerate() {
vehicle.accelerate();
}
}
public interface Vehicle {
public void accelerate();
}
@Component
public class Sedan implements Vehicle {
@Override
public void accelerate() {
System.out.println("speed up");
}
}
각 클래스에 @Component
어노테이션을 붙임으로서 컴포넌트 스캔의 대상이 되도록 작성했습니다. 이제 이들이 실제로 잘 등록되는지 확인하는 테스트 코드를 하나 작성해보겠습니다.
public class ComponentScanTest {
@Test
public void componentScanTest() {
//설정 정보를 가진 스프링 컨테이너 취득
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
//컨테이너로 부터 빈 취득
Driver driver = ac.getBean(Driver.class);
driver.pressAccelerate();
//driver 객체가 Driver 클래스의 인스턴스인가?
Assertions.assertThat(driver).isInstanceOf(Driver.class);
}
}
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
지정된 설정 정보를 갖는 스프링 컨테이너를 취득합니다.
ac.getBean(Driver.class);
등록된 빈을 컨테이너로부터 가져옵니다. 이름을 지정할 수도 있고, 지금 예제 코드처럼 타입을 지정할 수도 있습니다.
잘 등록이 되었다면 Assertions.assertThat(driver).isInstanceOf(Driver.class);
테스트 코드를 통과할 것이고 pressAccelerate()
메소드도 제대로 호출 되겠죠?테스트도 잘 통과했고, 메소드도 제대로 호출된 것을 알 수 있습니다. 이는 자동 등록이 잘 되었다는 이야기가 되겠죠?
@ComponentScan
은 기본적으로 자신이 위치한 패키지에서 스캔을 시작해서 하위의 모든 패키지를 다 돌며 스캔하게 됩니다. 옵션을 통해서 스캔을 시작할 위치를 변경할 수 있습니다.
@ComponentScan(basePackage = "경로")
는 지정한 경로에서 시작해 하위 패키지를 컴포넌트 스캔합니다.
이때 ,
쉼표를 이용해서 경로를 여러 개 지정할 수 있습니다.
@ComponentScan(basePackageClasses = 클래스파일명.class)
는 지정한 .class
클래스 파일 위치에서 시작해서 하위 패키지를 컴포넌트 스캔합니다.
최신 스프링/스프링 부트는 프로젝트의 root
위치에 이러한 설정 정보 파일을 두고 탐색 위치를 지정하지 않는 것을 권장하고 있습니다.
예를들어
com.hello.xxxxx
와 같은 프로젝트 구조를 만들었다면com.hello
의 위치에 메인 설정 정보 파일을 두고@ComponentScan
만 적어줍니다.
필터 옵션을 이용해서 스캔 대상에서 추가/제외할 수도 있습니다.
추가하는 경우에는 includeFilters
, 제외하는 경우에는 excludeFilters
옵션을 사용합니다.
//스캔 대상 추가
@ComponentScan(includeFilters = @Filter(type = FilterType.옵션, classes = 클래스.class))
//스캔 대상 제외
@ComponentScan(excludeFilters = @Filter(type = FilterType.옵션, classes = 클래스.class))
FilterType
은 5가지 상수 옵션을 제공합니다.
ANNOTAITION: 어노테이션을 인식한다. (default
)
ASSIGNABLE_TYPE: 지정한 타입과 그 자식 타입을 인식한다.
ASPECTJ: AspectJ 패턴
을 인식한다.
REGEX: 정규표현식을 인식한다.
CUSTOM: TypeFilter 인터페이스
를 구현해서 처리한다.