스프링 빈을 등록하는 방법에는 기존에 봐 왔던 AppConfig라는 외부 클래스의 각 메서드에 @Bean 애너테이션을 적용하거나 XML의 <bean> 태그를 통해 스프링 빈을 수동으로 등록하는 방법, 그리고 설정 정보 없이 자동으로 스프링 빈을 등록하는 방법이 있습니다.
컴포넌트 스캔
스프링 컨테이너 생성 시 @ComponentScan
애너테이션이 적용된 클래스를 파라미터의 인자로 넘겨주면 @Component
애너테이션이 적용된 클래스를 찾아 해당 클래스의 인스턴스를 생성해 스프링 컨테이너가 관리하도록 해줍니다. 즉 스프링 빈으로 등록합니다.
스프링이 자동으로 스프링 빈을 등록해 주는 방법을 채택하게 되면 @Component
애너테이션이 적용된 클래스에 애너테이션뿐만 아니라 약간의 변화를 주어야 합니다.
기존에는 설정 정보가 담긴 외부 클래스 AppConfig에서 필요한 객체 인스턴스(스프링 빈)를 명시해 넘겨주었습니다. 그런데 자동으로 등록해 주면서 이런 메서드가 없어졌기에 필요한 객체 인스턴스를 해당 클래스에서 자체적으로 해결해야 합니다.
그렇다고 해서 해당 클래스에서 직접 객체 인스턴스를 가리키도록 하면 객체 지향 프로그래밍 및 설계의 다섯 가지 기본 원칙 SOLID의 D인 "추상화에 의존해야지, 구체화에 의존하면 안 된다"의 의존 관계 역전 원칙(DIP, Dependency Inversion Principle)을 위반하므로 기존에 이를 지키려 했던 노력이 물거품이 됩니다.
그래서 스프링에서는 @Autowired
애너테이션을 준비해 개발자가 SOLID를 지키며 설계할 수 있도록 합니다. @Autowired
에 대한 자세한 내용은 나중에 본 블로그에서 다루거나 다른 블로그의 좋은 글들을 봐주시기 바랍니다. (#1, #2, ...)
컴포넌트 스캔과 의존 관계 자동 주입의 동작 과정
- 스프링 컨테이너를 생성하며 파라미터로 넘겨준 설정 정보 클래스를 토대로 스프링 빈을 등록합니다.
- 파라미터로 넘긴 클래스는
@ComponentScan
애너테이션이 적용되어 있어@Component
애너테이션이 적용된 모든 클래스를 스프링 빈으로 등록합니다. - 스프링 빈으로 등록하며 스프링 빈의 이름은 기본적으로 클래스 이름이되 첫 앞 글자는 소문자를 사용합니다. 즉 카멜 표기법을 사용합니다.
- 이름을 등록할 때 기본 전략이 싫다면
@Component
애너테이션을 적용하며name
속성을 지정하면 됩니다. 다음처럼 말이죠.@Component("memberService")
또는@Component(value = "memberService")
- 파라미터로 넘긴 클래스는
- 스프링 컨테이너가 해당 스프링 빈이 필요로 하는 스프링 빈을 찾아 의존 관계를 주입합니다.
컴포넌트 스캔 대상
탐색 시작 위치 지정
@ComponentScan
애너테이션이 적용된 클래스를 파라미터로 넘기면 스프링 컨테이너는 해당 클래스가 정의된 패키지부터 하위 패키지에 정의된 모든 클래스를 살펴봅니다. 만약 이러한 설정 정보 클래스를 별도의 패키지에 관리한다면 탐색 위치를 꼭 지정해 주어야 합니다.
basePackages
속성을 지정하면 속성의 값을 시작 위치로 하여 하위 패키지의 모든 클래스를 살펴봅니다.
@ComponentScan(
basePackages = "com.patulus.helloSpring.core",
)
시작 위치를 하나가 아닌 여러 개 지정할 수도 있습니다.
@ComponentScan(
basePackages = { "com.patulus.helloSpring.core",
"com.patulus.helloSpring.controller",
"com.patulus.helloSpring.domain",
"com.patulus.helloSpring.dto",
"com.patulus.helloSpring.repository",
"com.patulus.helloSpring.service"
},
)
basePackageClasses
속성을 지정하면 속성의 값인 클래스가 소속된 패키지를 시작 위치로 하여 하위 패키지의 모든 클래스를 살펴봅니다. 마찬가지로 시작 위치를 하나가 아닌 여러 개 지정할 수 있습니다.
@ComponentScan(
basePackageClasses = { BlogApiController.class,
Article.class
},
)
추가로 basePackageClasses
속성을 사용해 탐색 위치를 지정하면 추후 패키지나 클래스 이름이 바뀌었을 때 basePackages
속성은 문자열을 직접 변경해야 하지만 basePackageClasses
속성은 IDE의 리팩터링을 사용해 일괄적으로 변경할 수 있어 편리합니다. 두 속성을 동시에 사용할 수도 있습니다.
설정 정보를 프로젝트 최상단에 위치하면 좋아요
@ComponentScan
의 기본 탐색 시작 위치는 이 애너테이션이 적용된 클래스가 소속된 패키지와 그 하위 패키지입니다. 그렇기 때문에 굳이 basePackages
, basePackageClasses
속성을 적용하지 않고도 프로젝트 하위 클래스는 모두 컴포넌트 스캔의 대상이 됩니다.
추가로 Spring Boot에서는 @ComponentScan
애너테이션이 들어있는 @SpringBootApplication
애너테이션을 프로젝트 최상단에 위치시키고 있습니다.
컴포넌트 스캔 대상
다음의 애너테이션은 @Component
애너테이션을 포함하고 있어 자동으로 컴포넌트 스캔의 대상이 됩니다.
@Component
- 해당 애너테이션이 지정된 클래스는 컴포넌트 스캔의 대상이 되어 스프링 컨테이너 생성 시 자동으로 스프링 빈으로 등록됩니다.
(스프링 컨테이너 생성 시 객체 인스턴스가 만들어지며 스프링 컨테이너가 인스턴스를 관리합니다.)
- 해당 애너테이션이 지정된 클래스는 컴포넌트 스캔의 대상이 되어 스프링 컨테이너 생성 시 자동으로 스프링 빈으로 등록됩니다.
@Controller
- 해당 애너테이션은 Spring MVC의 컨트롤러임을 나타내며 Java Reflection을 통해 다양한 기능이 추가됩니다.
@Service
- 해당 애너테이션은 개발자에게 핵심 비즈니스 로직이 있음을 알립니다.
@Repository
- 해당 애너테이션은 스프링 데이터 접근 계층임을 나타내며 Java Reflection을 통해 다양한 기능이 추가됩니다. 또 데이터 계층의 예외를 스프링의 예외로 변환해 줍니다.
@Configuration
- 해당 애너테이션은 스프링 설정 정보임을 나타내며 스프링 빈이 싱글톤을 유지할 수 있도록 해줍니다.
이러한 애너테이션이 적용되면 자동으로 컴포넌트 스캔의 대상이 되지만 이들 중 일부만을 컴포넌트 스캔의 대상으로 하고 싶다면 useDefaultFilters
속성의 값을 false
로 적용하고 includeFilters
또는 excludeFilters
속성을 적용하면 됩니다.
필터
includeFilters
속성
해당 속성을 적용하면 해당하는 클래스를 스프링 빈으로 등록하도록 설정할 수 있습니다.
// MyIncludeComponent라는 사용자 정의 애너테이션을 만들고
// 해당 애너테이션이 적용된 클래스는 컴포넌트 스캔의 대상으로 합니다.
@ComponentScan(
includeFilters = @Filter(type = FilterType.ANNOTATION,
classes = MyIncludeComponent.class
)
)
excludeFilters
속성
해당 속성을 적용하면 해당하는 클래스를 스프링 빈으로 등록하지 않도록 설정할 수 있습니다.
// MyExcludeComponent라는 사용자 정의 애너테이션을 만들고
// 해당 애너테이션이 적용된 클래스는 컴포넌트 스캔의 대상이 되지 않도록 합니다.
@ComponentScan(
excludeFilters = @Filter(type = FilterType.ANNOTATION,
classes = MyExcludeComponent.class
)
)
컴포넌트 스캔 대상에서 해당 애너테이션 중 일부를 스프링 빈으로, 일부를 스프링 빈이 되지 않도록 설정하여 설정 정보 클래스를 나누어 관리할 수 있습니다.
// 이 애너테이션이 적용된 설정 정보 클래스는 @Controller 애너테이션이 붙은 클래스만 관리합니다.
@ComponentScan(
useDefaultFilters = false,
includeFilters = @Filter(type = FilterType.ANNOTATION,
classes = Controller.class
)
)
// 이 애너테이션이 적용된 설정 정보 클래스는 @Controller 애너테이션이 붙지 않은
// 다른 클래스를 관리합니다.
@ComponentScan(
excludeFilters = @Filter(type = FilterType.ANNOTATION,
classes = Controller.class
)
)
FilterType
은 ANNOTIATION
외에도 다양하게 있습니다. 자세한 내용은 여타 블로그의 글에서 확인할 수 있습니다. (#1, ...)
같은 빈 이름 등록과 충돌
자동 빈 등록 vs. 자동 빈 등록
컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록되면 ConflictingBeanDefinitionException
예외를 던져 같은 이름의 스프링 빈이 만들어지지 않도록 합니다.
수동 빈 등록 vs. 자동 빈 등록
수동으로 등록한 빈이 자동으로 등록한 빈을 오버라이딩하여 수동으로 등록한 빈만 사용할 수 있게 됩니다. 대개 의도적인 경우보다 여러 설정이 꼬여 생기는 경우로 해결하기 까다롭기에 빈이 같은 이름을 가지지 않도록 주의를 기울여야 합니다.
Spring Boot에서는 빈을 수동으로 등록했을 때의 이름과 자동으로 등록했을 때의 이름이 같으면 에러를 발생시키도록 기본 값을 설정하였습니다.