目次
- 序文
- 授業内容
-
- 1.Bean定義を生成する
- 2. 方法の説明
-
- 2.1 ClassPathBeanDefinitionScanner#scan
- 2.2 ClassPathBeanDefinitionScanner#doScan
- 2.3 ClassPathScanningCandidateComponentProvider#findCandidateComponents
- 2.4 クラスパスScanningCandidateComponentProvider#scanCandidateComponents
- 2.5 ClassPathScanningCandidateComponentProvider#isCandidateComponent
- 2.6 ClassPathScanningCandidateComponentProvider#isCandidateComponent
- 2.7 ClassPathBeanDefinitionScanner#postProcessBeanDefinition
- 2.8 AnnotationConfigUtils.processCommonDefinitionAnnotations
- 2.9 ClassPathBeanDefinitionScanner#checkCandidate
- 2.10 DefaultListableBeanFactory#registerBeanDefinition
- 3. スキャンロジックのフローチャート
- 4. Bean定義をマージする
- 要約する
序文
読書の準備
Springのソースコード解析は前後関係が強い処理であり、ここでの解析もコード順に説明しているため、事前知識を理解していないと、現在の内容を理解することができなくなります。したがって、私の以前の記事を (上から下まで) 読むことを特にお勧めします。
- Spring の根底にある核原理の分析——ガイド【学習難易度:★★☆☆☆】
- 手書き簡単Spring Container工程解析ガイド【学習難易度:★★☆☆☆】
- Springの基盤となるアーキテクチャの中核概念の分析[学習難易度:★★★☆☆、重要度:★★★★★ ]
- Beanのライフサイクルフローチャート【学習難易度: ☆☆☆☆☆、重要度: ★★★★★】
(追記: 特に「Bean のライフ サイクル フローチャート」は、最初に [目を開いて] プロセスを理解するのに役立ちます。結局のところ、[ビジネスを通じてコードを理解することは、コードを通じてビジネスを理解するよりもはるかに簡単です]!!!! )
(追記: 特に「Bean のライフ サイクル フローチャート」は、最初に [目を開いて] プロセスを理解するのに役立ちます。結局のところ、[ビジネスを通じてコードを理解することは、コードを通じてビジネスを理解するよりもはるかに簡単です]!!!! )
(追記: 特に「Bean のライフ サイクル フローチャート」は、最初に [目を開いて] プロセスを理解するのに役立ちます。結局のところ、[ビジネスを通じてコードを理解することは、コードを通じてビジネスを理解するよりもはるかに簡単です]!!!! )
読書ガイド
Spring の最も重要な機能は、プログラマがオブジェクト (つまり IOC) を作成できるようにすることです。Spring を開始することは、Bean オブジェクトの作成の準備をすることです。そのため、まず Spring がどのように Bean を作成するかを理解します。つまり、最初に Bean のライフサイクルを理解します。豆。
Bean のライフサイクルは、Bean が Spring でどのように生成され、破棄されるかを指します。
このレッスンの内容は、次のコードをエントリ ポイントとして説明します。
public class MyApplicationTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("org.tuling.spring");
Object newUser = context.getBean("newUser");
System.out.println(newUser);
}
}
主な用途は以下の Spring コンテナの構築方法です。
/**
* 创建一个新的AnnotationConfigApplicationContext,扫描给定包中的组件,为这些组件注册bean定义,并自动刷新上下文。
* 参数:
* baseppackages——要扫描组件类的包
*/
public AnnotationConfigApplicationContext(String... basePackages) {
this();
scan(basePackages);
refresh();
}
読書のアドバイス
- ソースコードを見て、細部に絡むことを忘れないでください。そうしないと、簡単に行き詰ってしまいます。通常はメインプロセスだけを見てください
- 理解できない場合は、クラスのアノテーションまたはメソッドのアノテーションを見てください。Spring は非常に優れたソース コードであり、注釈が適切に配置されています
- アイデアユーザーの方はF11のブックマーク機能をもっと活用してください。
- Ctrl + F11 ファイル/フォルダーを選択し、ニーモニックを使用してブックマークを設定/解除します (必須)
- Shift + F11 ポップアップブックマーク表示レイヤー (必須)
- Ctrl +1、2、3...9 対応する値のブックマーク位置に移動します (必須)
授業内容
1.Bean定義を生成する
1.1 簡単なレビュー
前回の記事を読んでいただいた方ならご存知かと思いますが、BeanDefiniton
豆の生成は豆を生産する前の【スキャン】の段階です。以下に示すように:
しかし、これは単なる単純な実装であるとも言いましたが、実際、Spring のこのスキャン プロセスの実装には 3 つのコア クラスと 10 のコア メソッドが含まれています。
※1.2 コンセプトレビュー
この [スキャン] プロセスには、Spring の基礎となる設計のいくつかの概念が含まれていました。前回のメモで紹介しましたが、覚えていない人は、戻って調べてください。
含まれる主な概念は次のとおりです。
- BeanDefinition (設計図): BeanDefinition は Bean の定義を表し、BeanDefinition には Bean の特性を記述する多くの属性があります。
- ClassPathBeanDefinitionScanner (シート登録): シートの登録に使用されます。
- BeanFacotory(Beanファクトリー):Beanを生成します。
DefaultListableBeanFactory
しかし実際には、このクラスのBeanDefinitionRegistry
インターフェースの登録機能がここで使用されます。
記事リンク:
「[Spring トピック] Spring の基礎となるアーキテクチャの中心となる概念の分析」
1.3 コアメソッドの説明
前述したように、スキャン プロセス全体には [3 つのコア クラス、10 のコア メソッド] が含まれます。以下、呼び出し順に各メソッドを順番に説明していきます。
2. 方法の説明
スキャン プロセス全体のエントリは次のコードのメソッドでありscan()
、最後の呼び出しは次のとおりです。ClassPathBeanDefinitionScanner.scan()
/**
* 创建一个新的AnnotationConfigApplicationContext,扫描给定包中的组件,为这些组件注册bean定义,并自动刷新上下文。
* 参数:
* baseppackages——要扫描组件类的包
*/
public AnnotationConfigApplicationContext(String... basePackages) {
this();
scan(basePackages);
refresh();
}
public void scan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
StartupStep scanPackages = this.getApplicationStartup().start("spring.context.base-packages.scan")
.tag("packages", () -> Arrays.toString(basePackages));
this.scanner.scan(basePackages);
scanPackages.end();
}
2.1 ClassPathBeanDefinitionScanner#scan
フルパス: org.springframework.context.annotation.ClassPathBeanDefinitionScanner#scan
メソッドのアノテーション: 指定された基本パッケージでスキャンを実行
ソースコードは次のとおりです。
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
しかし実際、本当の仕事はここではなく内部にありますdoScan()
2.2 ClassPathBeanDefinitionScanner#doScan
メソッド呼び出しチェーン: 2.1 の scan() によって呼び出されます。
フルパス: org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
メソッド アノテーション: 指定された基本パッケージ内をスキャンし、登録された Bean 定義を返します。このメソッドはアノテーション構成ハンドラーを登録しませんが、このジョブを呼び出し元に任せます。
ソースコードは次のとおりです。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
// 寻找候选的BeanDefinition
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
// 判断是否要设置BeanDefinition属性
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
// 判断是否要设置通用的注解BeanDefiniton属性
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
// 判断是否注册BeanDefinition
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
メソッドの解釈: このコードは多そうに見えますが、実際には全体としては比較的単純で、4 つのステップに分かれています
- まず、クラスパス内のすべてのクラスをスキャンして、候補 BeanDefinition を見つけます。何が候補なのかについては後述します
- 見つかった BeanDefinition の候補を調べて、BeanDefinition の一部のプロパティを設定する必要があるかどうかを判断します。デフォルトのプロパティなど
- 一般的なアノテーション BeanDefiniton 属性を設定するかどうかを決定します。
- BeanDefinition が重複する可能性があるため、BeanDefinition を登録するかどうかを決定します。
2.3 ClassPathScanningCandidateComponentProvider#findCandidateComponents
メソッド呼び出しチェーン: 2.2 の doScan() によって呼び出されます。
フルパス: org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents
メソッド アノテーション: クラスパス上の候補コンポーネントをスキャンします。
ソースコードは次のとおりです。
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
return scanCandidateComponents(basePackage);
}
}
メソッドの解釈: ここに 1 つありますif-else
が、通常は入力されますelse
。if
基本的に内部の状況を事業開発に利用することはなく、インデックス化の考え方を用いてパッケージのスキャンを高速化する戦略です。それを達成するにはどうすればよいでしょうか? resource
以下に新規spring.components
設定ファイル(KV形式)を作成することです。次のように:
org.tuling.spring.bean.User = org.springframework.stereotype.Component
Springにコンポーネントの候補を直接伝えることで、全クラスファイルをスキャンしてスクリーニングする工程が省かれ、効率が向上する。
2.4 クラスパスScanningCandidateComponentProvider#scanCandidateComponents
メソッド呼び出しチェーン: 2.3 の findCandidateComponents() によって呼び出されます。
フルパス: org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents
メソッド アノテーション: クラスパス上の候補コンポーネントをスキャンします。
ソースコードは次のとおりです。
private Set<org.springframework.beans.factory.config.BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
try {
// 读取类元信息
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// 第一层判断是否为候选组件,判断核心为:是否需要过滤
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
// 第二层判断是否为候选组件,判断核心为:是否独立、抽象类等
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
catch (FileNotFoundException ex) {
if (traceEnabled) {
logger.trace("Ignored non-readable " + resource + ": " + ex.getMessage());
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
メソッドの解釈: コードは非常に長く見えますが、実際は非常に単純です。大きく4つのステップに分かれる
- 分類子情報の読み取り (ディスクからの読み取り)
- まず、ResourcePatternResolver を通じて、指定されたパッケージ パスにあるすべての .class ファイルを取得します (このファイルは、Spring ソース コードの Resource オブジェクトにパッケージ化されています)。
- 各 Resource オブジェクトを反復処理します。
- MetadataReaderFactory を使用して Resource オブジェクトを解析し、MetadataReader を取得します (Spring ソース コードでは、MetadataReaderFactory の特定の実装クラスは CachingMetadataReaderFactory であり、MetadataReader の特定の実装クラスは SimpleMetadataReader です)。
- 候補コンポーネントかどうかを判断するには、フィルタリングするかどうか (最初のコンポーネント
isCandidateComponent
) が判断の核心となります。- MetadataReader を使用して、excludeFilters と includeFilters、および条件付きアノテーション @Conditional をフィルタリングします (条件付きアノテーションは理解されません。特定のクラスに @Conditional アノテーションがあるかどうか、存在する場合は、アノテーションで指定されたクラスの match メソッドを呼び出します)一致し、一致が成功した場合はフィルターを通過し、一致が失敗した場合は通過します。)
- 審査通過後、metadataReaderを元にScannedGenericBeanDefinitionを生成
- コンポーネント候補かどうかは、独立クラスか抽象クラスかなどが判断の核となります。(2 番目) (注:
isCandidateComponent
上記とisCandidateComponent
同じものではありません。メソッドのオーバーロードに属します) - スクリーニングに合格した場合、Bean がスキャンされたことを意味し、ScannedGenericBeanDefinition が結果セットに追加されます。
MetadataReader は、主に AnnotationMetadata を含むクラスのメタデータ リーダーを関数で表します。
- クラスの名前を取得し、
- 親クラスの名前を取得します
- 実装されているすべてのインターフェイス名を取得する
- すべての内部クラスの名前を取得します
- 抽象クラスかどうかの判定
- インターフェースかどうかの判断
- 注釈であるかどうかを判断する
- アノテーションを付けてメソッドのセットを取得する
- クラスに追加されたすべてのアノテーション情報を取得します
- クラスに追加されたすべてのアノテーション タイプのコレクションを取得します
2.5 ClassPathScanningCandidateComponentProvider#isCandidateComponent
メソッド呼び出しチェーン: 2.4 の scanCandidateComponents() によって呼び出されます。
フルパス: org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#isCandidateComponent
メソッド アノテーション: 指定されたクラスがどの除外フィルターにも一致せず、少なくとも 1 つの包含フィルターに一致するかどうかを判断します。
ソースコードは次のとおりです。
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
return false;
}
メソッドの解釈: これは、現在のクラスがフィルター除外オプションの範囲内にあるかどうかを確認するだけです。私の友人の多くは、@ComponentScan
注釈のincludeFilter
属性について考えていると思いますexcludeFilter
。それは完全に正しいわけではないとしか言えません。
コンテナの作成を開始すると、ClassPathBeanDefinitionScanner
デフォルトのフィルタリング ポリシーも追加されるためです。次のように:
/**
* 注册@Component的默认过滤器。
* 这将隐式注册所有带有@Component元注释的注释,包括@Repository、@Service和@Controller构造型注释。
* 还支持Java EE 6的javax.annotation.ManagedBean和JSR-330的javax.inject.Named注释(如果可用)。
*/
protected void registerDefaultFilters() {
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
}
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}
2.6 ClassPathScanningCandidateComponentProvider#isCandidateComponent
メソッド呼び出しチェーン: 2.4 の scanCandidateComponents() によって呼び出されます。
フルパス: org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#isCandidateComponent
メソッド アノテーション: 指定された Bean 定義が候補条件を満たすかどうかを判断します。デフォルトの実装では、クラスがインターフェイスではなく、それを囲んでいるクラスに依存しているかどうかがチェックされます。
ソースコードは次のとおりです。
/**
*确定给定的bean定义是否符合候选条件。
* 默认实现检查类是否不是接口,是否依赖于封闭类。
* 可以在子类中重写。
*/
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return (metadata.isIndependent() && (metadata.isConcrete() ||
(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}
メソッドの解釈: ここには、比較的馴染みのない 2 つのものがあります。それは、独立したクラスとルックアップ アノテーションです。
- 通常の内部クラスではなく、独立したクラス (静的内部クラスは独立したクラスです)
- 注釈を検索し、Baidu にアクセスして自分で使用します
この方法を要約すると、コンポーネント候補を判断するための条件は次のとおりです。
- まずは独立クラスです
- 特定のサブクラス、または Lookup アノテーションを持つ抽象クラスのいずれか
2.7 ClassPathBeanDefinitionScanner#postProcessBeanDefinition
メソッド呼び出しチェーン: 2.2 の doScan() によって呼び出されます
。 フルパス: org.springframework.context.annotation.ClassPathBeanDefinitionScanner#postProcessBeanDefinition
メソッド アノテーション: クラスパス上の候補コンポーネントをスキャンします。
ソースコードは次のとおりです。
/**
* 除了通过扫描组件类检索到的内容之外,还可以对给定的bean定义应用进一步的设置。
*/
protected void postProcessBeanDefinition(AbstractBeanDefinition beanDefinition, String beanName) {
beanDefinition.applyDefaults(this.beanDefinitionDefaults);
if (this.autowireCandidatePatterns != null) {
beanDefinition.setAutowireCandidate(PatternMatchUtils.simpleMatch(this.autowireCandidatePatterns, beanName));
}
}
メソッドの解釈: ここでは、主に BeanDefinition のいくつかのデフォルト プロパティが初期化されます。beanDefinition.applyDefaults(this.beanDefinitionDefaults);
ソース コードは次のとおりです。
public void applyDefaults(BeanDefinitionDefaults defaults) {
Boolean lazyInit = defaults.getLazyInit();
if (lazyInit != null) {
setLazyInit(lazyInit);
}
setAutowireMode(defaults.getAutowireMode());
setDependencyCheck(defaults.getDependencyCheck());
setInitMethodName(defaults.getInitMethodName());
setEnforceInitMethod(false);
setDestroyMethodName(defaults.getDestroyMethodName());
setEnforceDestroyMethod(false);
}
2.8 AnnotationConfigUtils.processCommonDefinitionAnnotations
メソッド呼び出しチェーン: 2.2 の doScan() によって呼び出されます。
フルパス: org.springframework.context.annotation.AnnotationConfigUtils#processCommonDefinitionAnnotations
メソッド アノテーション: アノテーションの蓄積されたクラス メタ情報に従って、BeanDefinition アノテーション関連のプロパティを設定します。
ソースコードは次のとおりです。
public static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd) {
processCommonDefinitionAnnotations(abd, abd.getMetadata());
}
static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {
AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
if (lazy != null) {
abd.setLazyInit(lazy.getBoolean("value"));
}
else if (abd.getMetadata() != metadata) {
lazy = attributesFor(abd.getMetadata(), Lazy.class);
if (lazy != null) {
abd.setLazyInit(lazy.getBoolean("value"));
}
}
if (metadata.isAnnotated(Primary.class.getName())) {
abd.setPrimary(true);
}
AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);
if (dependsOn != null) {
abd.setDependsOn(dependsOn.getStringArray("value"));
}
AnnotationAttributes role = attributesFor(metadata, Role.class);
if (role != null) {
abd.setRole(role.getNumber("value").intValue());
}
AnnotationAttributes description = attributesFor(metadata, Description.class);
if (description != null) {
abd.setDescription(description.getString("value"));
}
}
何も解釈する必要はなく、非常に単純な判断です
2.9 ClassPathBeanDefinitionScanner#checkCandidate
メソッド呼び出しチェーン: 2.2 の doScan() によって呼び出されます。
フルパス: org.springframework.context.annotation.ClassPathBeanDefinitionScanner#checkCandidate
メソッド アノテーション: 指定された候補 Bean 名をチェックして、対応する Bean 定義を登録する必要があるかどうかを判断します。既存の定義の競合に関連しています。
ソースコードは次のとおりです。
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
if (!this.registry.containsBeanDefinition(beanName)) {
return true;
}
BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
if (originatingDef != null) {
existingDef = originatingDef;
}
if (isCompatible(beanDefinition, existingDef)) {
return false;
}
throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +
"' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +
"non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");
}
メソッドの解釈: 通常、 を返すかtrue
、同じ名前を繰り返し定義しBean
て例外をスローします。false
通常、親子コンテナがある場合は、直接戻ります。
2.10 DefaultListableBeanFactory#registerBeanDefinition
メソッド呼び出しチェーン: 2.2 の doScan() によって呼び出されます。
フルパス: org.springframework.beans.factory.support.DefaultListableBeanFactory#registerBeanDefinition
メソッドのコメント: この登録センターを使用して、新しい Bean 定義を登録します。RootBeanDefinition と ChildBeanDefinition をサポートする必要があります。
ソースコードは次のとおりです。
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
}
else if (existingDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (logger.isInfoEnabled()) {
logger.info("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
existingDefinition + "] with [" + beanDefinition + "]");
}
}
else if (!beanDefinition.equals(existingDefinition)) {
if (logger.isDebugEnabled()) {
logger.debug("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
removeManualSingletonName(beanName);
}
}
else {
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
removeManualSingletonName(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
else if (isConfigurationFrozen()) {
clearByTypeCache();
}
}
メソッドの解釈: これはもう少し複雑なので、注意深く見ていません。しかもこれは非常に奥深くに隠されており、doScan()
内部から呼び出しているものの、実質的には継承AnnotationConfigApplicationContext
したメソッドを呼び出していることになります。GenericApplicationContext
registerBeanDefinition
3. スキャンロジックのフローチャート
- まず、指定したパッケージパス配下のすべてのクラスファイルをResourcePatternResolverで取得し、Resourceにパッケージ化します。
- 各 Resource オブジェクトを走査する
- ASM テクノロジーを使用して各ファイルを解析し、対応するクラスのメタ情報を取得し、それを MetadataReader にカプセル化します。
- フィルタリングには excludeFilter、includeFilter、および @Conditionnal アノテーションを使用します
- 審査通過後、metadataReaderを元にScannedGenericBeanDefinitionを生成
- そして、metadataReaderに基づいて、対応するクラスが独立クラス、インターフェース、抽象クラスのいずれであるかを判断します。
- スクリーニングに合格した場合、Bean がスキャンされたことを意味し、ScannedGenericBeanDefinition が結果セットに追加されます。
4. Bean定義をマージする
後続の IOC プロセスでは、Bean を生成するときに、次のような呼び出しが多数見られます。
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
これはいわゆる、マージされた BeanDefinition です。マージされた BeanDefinition とは何ですか? 実際、それは親子 BeanDefinition です。Javaの継承と同じです。ただし、これは通常、次のように XML 構成 Bean で発生します。
<bean id="parent" class="com.zhouyu.service.Parent" scope="prototype"/>
<bean id="child" class="com.zhouyu.service.Child"/>
ただし、このような定義の場合、子はプロトタイプ Bean になります。子の親 BeanDefinition は親であるため、親に定義されたスコープ属性を継承します。子に従って Bean オブジェクトを生成する前に、BeanDefinition をマージして、子の完全な BeanDefinition を取得する必要があります。
要約する
- Springのソースコードプロセスにおける[スキャン]の基礎となる原理を学びました