spring bootを使うにあたってコンポーネントスキャン周りを理解したかった
登場人物
- コンポーネント: アプリケーションのパーツ(リポジトリの実装とかアルゴリズムの実装とか)
- Springの世界ではDIコンテナで管理されているコンポーネントはBeanと呼ばれる(Bean定義じゃなくてBean実装)
- DIコンテナ: コンポーネントを登録する場所
- Configuration: コンポーネントをDIコンテナに登録するときに使用される設定ファイル
- Springの世界ではBean定義と呼ばれる
- コンポーネントを実装しているコードをBean定義と呼べばいいのにと思ったが、beanはmetadataとともに生成されるとのことで、コンポーネント(POJO)をDIコンテナから使えるようにbeanとして定義するconfigurationのことをBean定義と呼ぶと理解した。
- Springの世界ではBean定義と呼ばれる
- ApplicationContext: DIコンテナを触るためのインターフェース
- Configuration: コンポーネントをDIコンテナに登録するときに使用される設定ファイル
- アプリケーション: コンポーネントの利用者
- DIコンテナからBeanをルックアップする
spring bootのエントリーポイント
// SpringBootMyApplication.java
@SpringBootApplication
public class SpringBootMyApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootMyApplication.class, args);
}
}
spring bootでは@SpringBootApplication
アノテーションが重要な役割を持つ(ソース)
// SpringBootApplication.java
// 省略
@SpringBootConfiguration
@ComponentScan
// 省略
public @interface SpringBootApplication {
// 省略
}
@SpringBootApplication
は@ComponentScan
を継承している。@CompnenntScan
はSpringで定義されており、はvalue属性に指定したパッケージ(デフォルトでは付与されたクラスと同じパッケージ)配下のコンポーネントをスキャンしてBeanとして登録できる。たとえば、以下のように書く
@Configuration // configファイルであることの宣言
@ComponentScan("com.myexmple.app") // コンポーネントスキャン
public class AppConfig {
@Bean // メソッド名: bean名, 返り値: beanのインスタンス
MyRepository myRepository () {
return new MyRepositoryImpl();
}
@Bean
MyService myService(MyRepository myRepository) { // 他のコンポーネントを注入できる
return new MyServiceImpl(myRepository);
}
@Bean
MyService myService2 () {
return new MyServiceImpl2(); // 同じインターフェースを満たした違う実装
}
}
ConfigurationはBean定義でもあるので@Bean
というアノテーションを付けるとコンポーネントスキャンしたファイルをBeanとしてDIコンテナに登録してくれるが、Spring BootではコンポーネントのPOJOに@Component
のようなアノテーションを付けることでConfiguration側で@Bean
を用いたメソッドを作らなくてもDIコンテナに登録されるようになる。(この仕組はSpringのもの)
@ComponentScan
の動きを見ていく
まずエントリーポイントでSpringApplication.run(SpringBootMyApplication.class, args);
が実行される
ソース
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
上記のrunは以下を呼び出す
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
その後以下が呼ばれる
public ConfigurableApplicationContext run(String... args) {
// 省略
ConfigurableApplicationContext context = null;
// 省略
try {
// 省略
context = createApplicationContext();
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
// 省略
}
// 省略
}
createApplicationContext()
では ServletWebServerApplicationContext
か、AnnotationConfigReactiveWebServerApplicationContext
か、AnnotationConfigApplicationContext
のいずれかのクラスが生成される。前者2つはSpringBootで最後の一つはSpringで定義されている。いずれもAbstractApplicationContext
を継承している
refreshContextの定義は以下の通り
private void refreshContext(ConfigurableApplicationContext context) {
refresh((ApplicationContext) context);
// 省略
}
そしてrefreshが呼ばれる
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext);
refresh((ConfigurableApplicationContext) applicationContext);
}
protected void refresh(ConfigurableApplicationContext applicationContext) {
applicationContext.refresh();
}
createApplicationContext()
で生成したインスタンスのクラスはいずれもAbstractApplicationContext
を継承していたが、その中でrefreshは定義されている。ここでようやくSpringBootからSpring本体のコードに入る
// AbstractApplicationContext.java
public void refresh() throws BeansException, IllegalStateException {
// 省略
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 省略
try {
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// 省略
}
// 省略
}
invokeBeanFactoryPostProcessorsが呼び出される
// AbstractApplicationContext.java
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
// 省略
}
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessorsが呼び出される
// PostProcessorRegistrationDelegate.java
public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
// 省略
BeanDefinitionRegistryPostProcessor registryProcessor = (BeanDefinitionRegistryPostProcessor) postProcessor;
registryProcessor.postProcessBeanDefinitionRegistry(registry);
// 省略
}
ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry
// ConfigurationClassPostProcessor.java
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 省略
processConfigBeanDefinitions(registry);
// 省略
}
// ConfigurationClassPostProcessor.java
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
// 省略
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
// 省略
}
// 省略
// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
// 省略
parser.parse(candidates);
// 省略
}
@Configuration
の付与されたクラス(Configurationファイル)をparser.parse(candidates);
でパースしている。@Configuration
アノテーションは特に説明していなかったが、classに付与することでConfigurationファイルとして扱えるようにするためのアノテーションだ。@SpringBootApplication
-> @SpringBootConfiguration
-> @Configuration
という継承関係になっている。
ConfigurationClassParser.parse
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
// 省略
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
// 省略
}
}
それぞれparseメソッドを呼び出している。これらの定義は以下
// ConfigurationClassParser.java
protected final void parse(@Nullable String className, String beanName) throws IOException {
Assert.notNull(className, "No bean class name for configuration class bean definition");
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
processConfigurationClass(new ConfigurationClass(reader, beanName), DEFAULT_EXCLUSION_FILTER);
}
protected final void parse(Class<?> clazz, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(clazz, beanName), DEFAULT_EXCLUSION_FILTER);
}
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}
いずれにせよ、processConfigurationClass
を呼び出している
ConfigurationClassParser.processConfigurationClass
// ConfigurationClassParser.java
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
// 省略
SourceClass sourceClass = asSourceClass(configClass, filter);
do{
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);
// 省略
}
ConfigurationClassParser.doProcessConfigurationClass
// ConfigurationClassParser.java
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
// 省略
// Process any @ComponentScan annotations
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// 省略
}
}
// 省略
}
this.componentScanParser.parse
はComponentScanAnnotationParser.parse
である
ComponentScanAnnotationParser.parse
// ComponentScanAnnotationParser.java
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
// 省略
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
ClassPathBeanDefinitionScanner.doScan
// ClassPathBeanDefinitionScanner.java
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 省略
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
// 省略
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
// 省略
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
// 省略
}
registerBeanDefinition
でbean定義を登録していると思われる