この記事GitHubhttps ://github.com/JavaFamilyが含まれています。完全なテストサイト、資料、および一次メーカーへのインタビュー用の一連の記事があります。
序文
同僚が急いでやって来て言った:アオビン、このエラーを見るのを手伝ってください、状況はどうですか?
エラーが表示されます:
Field xxxService in com.xx.xx.service.impl.XxXServiceImpl required a bean of type 'com.xx.xx.service.XxxService' that could not be found.
実は何が起こっているのかはすでに知っていたのですが、彼にはわからないのではないかと心配して、辛抱強く説明しました。
私のような背骨を持っている人として、私はそれについて考える必要さえないので、私は次のことを持っています:
このエラーは、実際にはBeanがSpringコンテナで見つからないことです。このエラーが発生した場合、2つの一般的な状況があります。
1.@ComponentScan
注釈のスキャンパスにこのクラスが含まれていません
2.このクラスのヘッダーには@Component
注釈がありません
次に、疑問が生じます。@ComponentScan
スキャンや注釈なし@Component
でSpringコンテナに注入できないのはなぜですか。この質問は少し無意味です(@Component
注釈なしでSpringコンテナに挿入したいですか?)
別の方法で質問させてください。@ComponentScan
スキャンして注釈を付けた@Component
後、なぜSpringコンテナに注入できるのでしょうか。
もちろん、あなたは直接答えることができます:春がこれを規定しているので
もちろん、私はあなたに尋ね続けます:Mybatisのマッパーには@Component
注釈がありません、それでなぜそれをSpringコンテナに注入することができますか?
ばか、答えられない?答えられないなら見下ろしてください〜
問題分析
答えるには:なぜそれを@ComponentScan
スキャンして@Component
注釈を付け、Springコンテナに注入できるのですか?
まず、問題を分解する必要があります。
1.@ComponentScan
スキャンは何をしますか?
2.@Component
注釈はどういう意味ですか?
これらの2つの質問に答えた後、推測してみましょう。上記のプロセスをカスタマイズできますか?カスタマイズする方法は?そうしないと、マッパーがどのようにSpringコンテナーに注入されたかを知る方法がありません。
@ComponentScan
スキャンは何をしますか?
プロセスはおおまかに次のようになります。Springは指定されたパッケージの下のクラスをスキャンし、これらのクラスの情報を解析してBeanDefinitionに変換し、beanDefinitionMapに登録します。
では、このプロセスの詳細は何ですか?
まず、このプロセスに関係する役割を見てみましょう。
1. BeanDefinition:クラスに関する情報を含むBean定義
2. ConfigurationClassPostProcessor:構成クラスプロセッサ、構成クラスの検索、構成クラスパーサーの作成
3. ConfigurationClassParser:クラスパーサーの構成、構成クラスの解析、@ComponentScanアノテーションパーサーの作成
4. ComponentScanAnnotationParser:@ComponentScanアノテーションパーサー、@ ComponentScanアノテーションの解析、Bean定義スキャナーの作成
5. ClassPathBeanDefinitionScanner:Bean定義スキャナーは、指定されたパッケージの下のすべてのクラスをスキャンし、準拠するクラスをBeanDefinitionに変換します
6、BeanDefinitionRegistry:BeanDefinitionレジスタ、BeanDefinitionを登録します
上から下に見ると、このプロセス全体が進歩的な関係にあることが簡単にわかります。
これらの役割の具体的な責任を見てみましょう。
1.構成クラスプロセッサ
構成クラスプロセッサは主に3つのことを行います
1.構成クラスを見つけます
2.構成クラスパーサーを作成して呼び出します
3.構成クラスパーサーによって返される@Importおよび@Bean注釈付きクラスをロードします
1.1構成クラスを見つける
疑問があるかもしれませんが、構成クラスは私たちから渡されたものではありませんか?なぜ構成クラスを検索する必要があるのですか?
これは、Springの呼び出しチェーン全体が非常に複雑であるためです。構成クラスを下位層に渡すことはできませんが、最初からBeanDefinitonMapに構成クラスを登録します。
構成クラスを見つけるには、おおよそ2つのプロセスがあります。
1.BeanFactoryからすべてのBeanDefiniton情報を取得します
2.BeanDefinitonが構成クラスであるかどうかを判別します
最初のステップは非常に簡単に解決できます。すべてのBeanDefinitonsはBeanFactoryのBeanDefinitonMapに配置され、そこから直接取得できます。
2番目のポイントとして、最初に構成クラスとは何かを知る必要がありますか?
Springには、2つの構成クラスがあります。
1.フルタイプ:@Configuration
アノテーションでクラスを識別します
2、ライトタイプ:@Component
@ComponentScan
@Import
@ImportResource
@Bean
注釈でクラスを識別します(そのうちの1つで十分です)
それらの唯一の違いは、full型のクラスが後処理ステップで動的にプロキシされることです。
この例を覚えていますか?
@Configuraiton
public class MyConfiguration{
@Bean
public Car car(){
return new Car(wheel());
}
@Bean
public Wheel wheel(){
return new Wheel();
}
}
Q:Springが開始されたとき、Wheelオブジェクトは何回新しいですか?
MyConfigurationオブジェクトは実際にはcglibによって動的にプロキシされるため、答えは1つです。そのためthis.
、メソッドによって呼び出された場合でも、プロキシロジックはトリガーされます。
これはこの場合にのみ当てはまります。通常、cglibプロキシを実行すると、この呼び出しは引き続きこのクラスのメソッドを直接呼び出します。
すべての構成クラス情報が見つかったら、次のステップは、構成クラスパーサーを作成し、すべての構成クラスを構成クラスパーサーに渡して解析することです。
1.2フローチャート
2.クラスパーサーを構成します
構成クラスパーサーの責任は次のとおりです。
1.クラスが解析をスキップする必要があるかどうかを判断します
2.内部クラス情報を解析します
3.@PropertySources
注釈情報を解析します
4.@ComponentScan
注釈情報を解析します
5.@Import
注釈情報を解析します
6.@Bean
注釈情報を解析します
2.1クラスが解析をスキップする必要があるかどうかを判断する
クラスが解析をスキップする必要があるかどうかのいわゆる判断は、クラスが@Conditional
注釈でマークされているかどうか、およびクラスが条件を満たすかどうかを実際に判断することです。注釈が識別され、条件が満たされない場合、解析ステップはスキップされます。
よく見られるよう@Profile
に、@ConditionalOnMissBean
などはこれによって制御されます。
2.2内部クラス情報を解析する
構成クラスに内部クラスがあり、内部クラスも構成クラスである場合があるため、この方法で解析する必要があります。
2.3@ComponentScan
注釈情報の解析
この手順では、主に** @ ComponentScanアノテーションパーサーを使用してアノテーションを解析し@ComponentScan
、BeanDefinitionリストを取得してから、これらのBeanDefinitionsが構成クラスであるかどうかを判断し、再帰分析のために構成クラスパーサー**を再度呼び出します。
フローチャート
3.@ComponentScanアノテーションパーサー
このステップでは、Springは@ComponentScan
アノテーションに設定したすべての情報を抽出し、それをBean Definition Scannerに格納してから、BeanDefinitionScannerを使用して修飾されたBeanDefinitonを取得します。
ExclusiveFilterとincludeFilterは、クラスがスキャン中に要件を満たしているかどうかを判断するために使用されます。
デフォルトのexcludeFilter:このクラスをスキャンから除外します
デフォルトのincludeFilter:スキャン時にクラスが@Componentアノテーションを識別するかどうかを決定します
4.Bean定義スキャナー
BeanDefinitionScannerは、主に次の3つのことを行います。
1.パッケージパスの下のクラスをスキャンします
2.BeanDefinitonの値を設定します
3. BeanDefinitionレジストラを使用して、BeanDefinitonをコンテナに登録します
4.1パッケージパスの下のクラスをスキャンします
パッケージパスをスキャンするステップは、クラスファイルをトラバースし、パッケージ内の各クラスをトラバースし、クラスが条件を満たしているかどうかを判断するプロセスとして簡単に理解できます-@Component
アノテーションをマークし、条件を満たしているクラスをBeanDefinitonに変換します。現時点では、BeanDefinitonにはmetedata情報のみがあり、特定の設定はありません。
4.2BeanDefinitonに値を設定します
次のようなアノテーションをクラスに追加する場合:@Lazy @Primary @DependsOn、これらのアノテーションを実際のプロパティに変換し、BeanDefinitonに設定する必要があります。
4.3フローチャート
5.BeanDefinitionレジストラ
BeanDefinitionRegistryの役割は、BeanDefintonをBeanDefintonMapに配置することです。
考える
パッケージをスキャンする全体的なプロセスがわかったので、次の質問を確認しましょう。MybatisのマッパーはどのようにSpringコンテナに注入されますか?
このような質問は一見理解しにくく、面接官が答えを持って質問しているため、面接の状況でよく発生します。
しかし、考えてみると、角度を変える必要があります。マッパーをSpringに登録するにはどうすればよいですか?>カスタムアノテーションで識別されるクラスをSpringに登録するにはどうすればよいですか?
これが質問しやすいかどうかわかりませんか?
方法
1.TypeFilterを使用します
アノテーションはデフォルトで登録されたIncludeFilterで使用されることがわかっている@Component
ため、カスタムアノテーションでカスタムIncludeFilterを使用することもできます
カスタムマッパーアノテーション
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Mapper {
}
マッパーを使用する
@Mapper
public class MyMapper {
public void hello(){
System.out.println("myMapper hello");
}
}
テスト
テスト用のカスタムIncludeFilterを追加します
**注:**このメソッドは、エンティティクラスでカスタムアノテーションがマークされている場合にのみサポートできます。マッパーアノテーションがインターフェースに追加されると、例外が発生します:「myMapper」という名前のBeanは使用できません
インターフェイスをインスタンス化できないため、答えは非常に単純です。そのため、Springはデフォルトで、クラスがエンティティクラスでない場合、コンテナに登録されないと判断します。
では、Springに登録されたMapperでインターフェイスに注釈を付けるにはどうすればよいでしょうか。
2.カスタムスキャナー
Springのスキャナーはインターフェースをサポートできないため、それを書き直します-判断ロジック。
オープンソースフレームワーク拡張エクスペリエンス:全体的なロジックを継承し、ロジックの一部を書き直します。
したがって、このメソッドは非常に単純です。ClassPathBeanDefinitionScannerを継承し、クラスが準拠しているかどうかを判断するロジックを書き直します。
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
// 重写判断beanDefinition是否为接口逻辑,改为只有类为接口时才允许注册
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
//省略构造方法
}
ロジックが変更され、Springにそれをどのように使用させるかという新しい質問があります。
全体的なプロセスを通じて、** @ ComponentScanアノテーションパーサー**の解析プロセスでBean定義スキャナーが(新しく)作成され、このプロセスを変更できないことがわかっています。つまり、ゲームオーバーですか?
しかし、なぜSpringのスキャンプロセスでスキャナーを使用する必要があるのでしょうか。Springのスキャンプロセスが終了した後、もう一度スキャンできますか?
これを行う方法を覚えていますか?ポストプロセッサー!
3.ポストプロセッサを使用する
BeanDefinitionRegistryPostProcessorを使用して、Springのスキャンプロセスが終了した後に後処理を行います。後処理では、2回目のスキャン用にカスタムスキャナーが作成されます。
@Component
public class MapperScannerProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// 创建出自定义的扫描器
ClassPathMapperScanner classPathMapperScanner = new ClassPathMapperScanner(registry, false);
// 添加filter,class添加了Mapper注解才注册到Spring中
classPathMapperScanner.addIncludeFilter(new AnnotationTypeFilter(Mapper.class));
// 这里可以改为从外部设值,不必写死
classPathMapperScanner.scan("com.my.spring.test.custom");
}
}
このメソッドを使用すると、インターフェイスが実際にBeanDefinitionMapに登録されていることがわかります。
ただし、それでもエラーが発生します:[com.my.spring.test.custom.InterfaceMapper]のインスタンス化に失敗しました:指定されたクラスはインターフェースです
Springに登録しましたが、インターフェイスは実際にはインスタンス化できません。しかし、Mybatisはどのようにそれを行いますか?
答えは、Mybatisが図のbeanClassをFactoryBean:に置き換えてからMapperFactoryBean
、元のbeanClassをそのmapperInterfaceプロパティに配置することです。
そのgetObjectメソッドは次のようになります
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
Mybatisの元の使用法を覚えていれば、このコード行はおなじみのはずです。
さて、思考の内容はここにあります。Mybatisの現象を借りて考え、Mybatisの内容をさらに深く掘り下げます。
まとめ
この記事では、開発中の一般的な問題を分析し、Springの構成クラスの解析とスキャンのプロセスを紹介し、Mybatisの現象を使用して、Springにカスタムアノテーションクラスを登録する方法を検討します。ケースを通してプロセスをより深く理解することができます。
同様に、この調査を通じて、次の質問に答えます
1.構成クラスとは何ですか?Springにはどのような構成クラスがありますか?違いは何ですか?
2. BeanDefinitionRegistryPostProcessorの用途は何ですか?何かケースを知っていますか?
あなたは心の中で、良い男アオビンが放課後に宿題をやめることを学んだと思いますか?
私はアオビンです、あなたが知っているほど、あなたは知らない、あなたの才能に感謝します:のように、お気に入りとコメント、次の号でお会いしましょう!
記事は継続的に更新されており、WeChatで「 AoBing 」を検索してできるだけ早く読んで、[情報]に返信することができます。インタビュー資料と一流メーカーの履歴書テンプレートを用意しています。この記事にはhttps://github.com/JavaFamilyGitHub