目次
序文
春、Java プレーヤーのほとんどは [最もよく知られた見知らぬ人] であると言えます。説明する 8 つの単語: 半分理解しました、
簡単な応用と言いますか、誰もがそれを知っています、本当に展開して 2 つの文を言いたい場合は、この 2 つの文しかありません。これが最初の文で、その後に次の文が続きます。 2 番目の文、まあ、終わりました。
でもああ、xdm、Spring は非常に優れたソースコードだと言われており、デザインパターンの適用シナリオが豊富なだけでなく、コードが美しく整理されているので、ぜひ学習することをお勧めします。日常生活の中でごっこ遊びができるだけでなく、知識を豊かにし、コードを書く能力も向上します。
ナビゲーションを読む
対象読者:Spring開発経験のある方
予備知識
Q1: JVM オブジェクトの作成プロセスについて説明していただけますか?
答え: 写真を見て話してください:
- クラスのロード: クラスを使用する前に、Java 仮想マシンはクラスのバイトコードをメモリにロードする必要があります。クラスのロードは Java 仮想マシンの中核プロセスであり、クラスのバイトコード ファイルを見つけてメモリのメソッド領域にロードする役割を果たします。クラスのロードには、ロード、検証、準備、解析、初期化の 5 つのフェーズが含まれます。
- メモリの割り当て: クラスのロードが完了すると、Java 仮想マシンはオブジェクトにメモリ領域を割り当てます。メモリ割り当ては通常、ヒープ (Heap) 上で実行されますが、スレッド スタック上のローカル オブジェクトなど、特殊な場合にはスタック (Stack) 上にメモリを割り当てることができるオブジェクトもあります。
- インスタンス化 (ゼロ値の初期化): メモリーを割り当てた後、Java 仮想マシンはオブジェクトのメモリー空間をゼロ値に初期化します。これには、プリミティブ型のデフォルト値 (0、false など) と参照型のデフォルト値 (null) が含まれます。
- オブジェクト ヘッダーの設定: メモリ内の Java オブジェクトのレイアウトには、オブジェクト ヘッダーとインスタンス データの 2 つの部分が含まれます。オブジェクト ヘッダーには、オブジェクトのハッシュ コード、ロック ステータスなどのメタデータが保存されます。オブジェクトの作成中に、Java 仮想マシンはオブジェクト ヘッダーの値を設定します。
- コンストラクターの実行: オブジェクト作成の最後のステップは、コンストラクターを実行することです。コンストラクターは、オブジェクトのインスタンス データを初期化し、その他の必要な初期化操作を実行するために使用されます。コンストラクターは、クラスのデフォルト コンストラクターまたはカスタム コンストラクターにすることができます。
- オブジェクト参照を返す: オブジェクトが作成された後、Java 仮想マシンはオブジェクトへの参照を返します。参照を通じて、プログラムはオブジェクトのプロパティとメソッドを操作できます。
(追伸:なぜこの質問をするのですか?Spring は IOC テクノロジーであるため、どれだけ機能しても、この基本プロセスに従ってオブジェクトを作成する必要があります。ただし、Spring IOC はこのプロセスで多くの新しいスロットを追加し、ホットスワップによって IOC の機能を強化しました。)
Q2:春の特徴は何ですか?
回答: Spring の特徴は IOC と AOP の 2 つの概念です。次のようにも言えます。Spring は、AOP テクノロジーを実装する IOC コンテナーです。(容器,容器,容器)
Q3: IOC と AOP とは何ですか?
回答: 次の回答は Baidu [Wen Xin Yi Yan] からのものです。
- IOC (Inversion of Control)は、コード内でオブジェクトを直接作成するのではなく、オブジェクトの作成と管理を Spring コンテナーで処理できるようにする設計パターン (考え方) です。IOC を使用すると、オブジェクトの依存関係をコードから切り離すことができ、コードがより柔軟になり、保守しやすく、テストしやすくなります。
- AOP (アスペクト指向プログラミング) も、プリコンパイルとランタイム動的プロキシを使用して、ソース コードを変更せずにプログラムに関数を動的に追加するデザイン パターン (思想) です。AOP は、トランザクション管理、セキュリティ、ロギングなど、オブジェクト指向プログラミングでは解決できない問題を解決します。
Spring フレームワークは、IOC と AOP を実装することにより、プログラムをよりモジュール化して柔軟性を高め、保守を容易にします。同時に、Spring は DAO、ORM、WebMVC などの他の多くのモジュールや機能も提供しており、Spring を強力な Java 開発フレームワークにしています。
事前知識のまとめ
上記の質問から、非常に重要なことが分かりました。Spring は、AOP テクノロジーを実装する IOC コンテナーです。さらに、IOC と AOP の概念についても概説します。IOC が実際にオブジェクトの作成を管理していることもわかったので、オブジェクトの作成に関しては、Q1 で述べたオブジェクト作成のプロセスと切り離せないはずです。さらに、オブジェクトがどのように作成されたか、誰が作成したかに関係なく、上記のプロセスから離れることはできません。
実際、事前に言えることは、IOC でのオブジェクト作成プロセスは、Spring 関数の実現をサポートするために、上記のオブジェクト作成プロセスの一部の詳細を強化し、いくつかの拡張ポイントを追加したものにすぎないということです。
授業内容
Spring のソース コードに関する調査を実行するために、ここでは Spring の核となる知識ポイントについて簡単に説明します。これにより、誰もが Spring の根底にある基本的なロジックを明確に理解できるようになります。
1. Springコンテナの起動
SSM/SSH 時代を経験した友人なら、次のコードに精通していると思います。
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();
System.out.println(userService);
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
<!-- <import resource="引入其他bean xml配置文件" />-->
<bean id="userService" class="org.example.spring.bean.UserService"/>
</beans>
本当に馴染みがなくても問題ありませんが、次の方法は比較的馴染みがあるかもしれません。 (この起動方法の Spring についても後で説明します。以下のより主流のものに加えて、次の方法がより一般的であるためでもあります)広く使用され、更新され、コンテンツが比較的豊富な点! )
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.test();
System.out.println(userService);
@Component
public class UserService {
public void test() {
System.out.println("这是一个测试方法");
}
}
あはは、Java [SpringBoot] の時代に直接入った友人の多くは、上記の内容さえ見たことがないかもしれません。
上記のコードは何をするのでしょうか? 非常に簡単で、Spring コンテナを起動するだけです。上記の 2 つの異なる起動方法は、単に Bean の登録方法が異なるだけです。例えば前者はxml
内のタグを読み込んで定義し<bean>
、後者は読み込んだアノテーションBeanとします。
この時点で、質問したいのですが、上記 2 番目のメソッドのコードを通じて、何がわかりましたか? 私の発見は、コード行を呼び出すだけで、Spring によって定義された Bean の使用を開始できるということであり、依存関係注入や AOP などは気にせず、直接実行するだけです。これは何を証明するのでしょうか?実際、それは表面的で少しナンセンスですが、それが証拠です。このコード行を通じて、私たちが通常使用する Spring の基本機能をすべて完了するのに役立ちます。。
2. 一般的なプロセスの推測
これまでに学習した Spring 関連の操作に従って、このコード行で何が行われるかを単純に推測できます。
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
2.1 スキャン
まずは最初のポイント【スキャン】です。プロジェクトには非常に多くの Bean を記述しました。つまり、プロジェクトには非常に多くのクラス (Bean と通常のクラス) が存在します。Spring はそれをどのように認識するのでしょうか? 実はその理由は非常に単純で、Spring はそこまで賢くないので、このクラスの情報を取得したい場合、Spring はこのクラスの具体的な情報を知るために [直接] 見なければなりません。ファイルはいくつありますか。スキャンするクラスはいくつありますか。キーコードは次のとおりです。
// 定义需要扫描的基础包名
@ComponentScan("org.tuling.spring")
public class AppConfig {
}
2.2 IOC
すべてのファイルをスキャンした後、Spring は基本的にどれが Bean でどれが通常のクラスであるかを判断できます。次に、Bean の作成を開始できます。これがいわゆる IOC プロセスです。
2.3 AOP
AOP は IOC の後に発生したはずですが、デザイン パターンの [プロキシ パターン] を理解していれば、これを理解するのは難しくありません。結局のところ、ターゲット オブジェクトが完全に機能しない場合、プロキシ オブジェクトの機能も影響を受けます。
2.4 概要
3. [スキャン] プロセスは推測が簡単です
また、スキャンには Spring がどのものを作成する必要があり、どのものを作成する必要がないのかを直接確認する必要があるとも前に述べました。私たちの例を見てみましょうnew AnnotationConfigApplicationContext(AppConfig.class)
。一般的な手順は次のとおりです: (単純な推測、不明)
AppConfig.class
最初に調べて、スキャンされたパッケージのベースパスを読み取る必要があります@Component
前のステップで読み取った基本パスに従って、パッケージ配下のすべてのファイルを走査し、クラスになどのアノテーションがあれば@Service
Beanであることが確認されます。- フィルタリング後、読み取られた Bean 情報を記録します。たとえば、以降の走査のためにマップに保存します。
4. 【IOC】単純な推測プロセス
実際、IOC プロセスには、Spring では「Bean ライフ サイクル」と呼ばれる、より専門的な用語が追加されています。いくつかの簡単な単語には多くの内容が含まれています。その前に、【事前知識】の【JVMオブジェクト作成プロセス】を見て、印象を深めてみましょう。
含む:
- このクラスの構築メソッドを使用してオブジェクトをインスタンス化します (ただし、クラス内に複数の構築メソッドがある場合、Spring が選択します。これは推論された構築メソッドと呼ばれます)。
- オブジェクトを取得した後、Spring はオブジェクト内に @Autowired アノテーションが付けられた属性があるかどうかを判断し、それらの属性を見つけて Spring によって値を割り当てます (依存性インジェクション)
- 依存関係の注入後、Spring はオブジェクトが BeanNameAware インターフェース、BeanClassLoaderAware インターフェース、および BeanFactoryAware インターフェースを実装しているかどうかを判断します。実装されている場合、現在のオブジェクトは定義された setBeanName()、setBeanClassLoader()、および setBeanFactory() メソッドを実装する必要があることを意味します。次に、Spring はこれらのメソッドを呼び出し、対応するパラメータを渡します (Aware コールバック)。
- Aware コールバック後、Spring はオブジェクト内に @PostConstruct アノテーションが付けられたメソッドがあるかどうかを判断し、存在する場合は現在のオブジェクトのメソッドを呼び出します (初期化前)。
- その直後、Spring はオブジェクトが InitializingBean インターフェースを実装しているかどうかを判断し、実装されている場合、現在のオブジェクトはインターフェースに afterPropertiesSet() メソッドを実装する必要があることを意味し、Spring は現在のオブジェクトの afterPropertiesSet() メソッドを呼び出します。 (初期化)
- 最後に、Spring は現在のオブジェクトが AOP を実行する必要があるかどうかを判断し、そうでない場合は Bean を作成し、AOP が必要な場合は動的プロキシを実行し、プロキシ オブジェクトを Bean として生成します (初期化後)。
もう 1 つ注意すべき点は、Bean オブジェクトが作成された後であることです。
- 現在の Bean がシングルトン Bean の場合、Bean オブジェクトは Map<String, Object> に格納され、Map のキーは beanName、値は Bean オブジェクトになります。このようにして、次回 getBean を実行するときに、対応する Bean オブジェクトを Map から直接取得できます (実際、Spring ソース コードでは、この Map はシングルトン プールです)。
- 現在のBeanがプロトタイプBeanの場合は、今後何もアクションはなく、Mapも格納されないため、次回getBean時に上記の作成処理が再度実行され、新しいBeanオブジェクトが取得されます。
4.1 構築方法の推定プロセスの詳細な説明
Spring は、特定のクラスに基づいて Bean を生成するプロセスで、そのクラスの構築メソッドを使用してオブジェクトをインスタンス化する必要がありますが、クラスに複数の構築メソッドがある場合、Spring はどれを使用するのでしょうか?
Spring の判定ロジックは次のとおりです。
- クラスにコンストラクターが 1 つしかない場合、コンストラクターにパラメーターがあるかないかに関係なく、選択肢がないため、Spring はこのコンストラクターを使用してオブジェクトを作成します。
- このクラスに複数のコンストラクターがある場合:
- 引数のないコンストラクターが存在する場合は、引数のないコンストラクターを使用します。Java では、引数のないコンストラクター自体がデフォルトの意味を持っているためです。
- 引数のないコンストラクターがない場合は、複数の引数のないコンストラクターを確認し、
@Autowired
変更されているものを選択します。コンストラクターがない場合は、エラーを報告することしかできません。
まだ質問があります。Spring がパラメータ付きの構築メソッドを選択した場合、Spring はパラメータ付きでこの構築メソッドを呼び出すときにパラメータを渡す必要があります。このパラメータはどのようにして取得されるのでしょうか? 答えは、Spring は入力パラメータの型と名前に従って Spring で Bean オブジェクトを見つけます。
3. まず入力パラメータのタイプに従って検索し、1 つだけ見つかった場合は、それを入力パラメータとして直接使用します;
4. タイプに従って複数が見つかった場合は、入力パラメータ名に従って唯一のパラメータを決定します;
5. 最後に、見つからない場合はエラーが報告され、現在の Bean オブジェクトを作成できません。
5. [AOP] 単純な推測プロセス
AOP は動的プロキシです。Bean の作成プロセスでは、Spring が最後のステップにあります (シングルトンプールに入れる前に) は、現在作成中の Bean が AOP を実行する必要があるかどうかを判断し、必要に応じて動的プロキシを実行します。
では、Bean が AOP によってプロキシされる必要があるかどうかをどのように判断すればよいでしょうか? 次のように進めます。
- すべてのファセット Bean を検索します (ファセットも Bean からのもの、または特別な Bean と呼ばれます)。
- アスペクト内の各メソッドをトラバースして、@Before、@After、およびその他のアノテーション (通知) が書き込まれているかどうかを確認します。
- 記述されている場合は、対応するPointcutが現在のBeanオブジェクトのクラスと一致するか判定します。
- 一致する場合、現在の Bean オブジェクトに一致する Pointcut があることを意味し、AOP が必要であることを意味します。
cglib を使用した AOP の一般的なプロセス: (上記のプロキシ パラダイムを参照)
- プロキシ クラス XxxProxy を追加します。これはプロキシ オブジェクト XxxTarget から継承し、XxxTarget メンバー変数を保持します (このメンバー変数は Bean 宣言サイクル、つまり完全な IOC などを通過する必要があります)。
- プロキシクラスで親クラスのメソッドをオーバーライドします。
- プロキシクラスのメソッドを実行する場合、プロキシクラスのメソッドが呼び出されますが、同時にアスペクトのロジックも実行する必要があります
次に、[プロキシ モード] のパラダイムを示します。
// 被代理对象
public class ProxyTarget {
public void run() {
System.out.println("这是普通对象的run");
}
}
// 代理对象
public class ProxyModel extends ProxyTarget {
private ProxyTarget proxyTarget;
public void setProxyTarget(ProxyTarget proxyTarget) {
this.proxyTarget = proxyTarget;
}
@Override
public void run() {
System.out.println("我代理对象可以在这里做加强---1");
super.run();
System.out.println("我代理对象也可以在这里做加强---2");
}
}
六、春事
@Transactional アノテーションをメソッドに追加すると、メソッドが呼び出されたときに Spring トランザクションが開始され、メソッドが配置されているクラスに対応する Bean オブジェクトがそのクラスのプロキシ オブジェクトになることを意味します。
Springトランザクションのプロキシオブジェクトが特定のメソッドを実行する際の手順は以下のとおりです。
- 現在実行中のメソッドに @Transactional アノテーションが付いているかどうかを確認します。
- 存在する場合は、トランザクション マネージャー (TransactionManager) を使用して新しいデータベース接続を作成します。
- データベース接続の自動コミットを false に変更します。
- target.test() を実行し、プログラマが作成したビジネス ロジック コードを実行します。つまり SQL を実行します。
- 実行後、例外がなければサブミット、例外がなければロールバック
Springトランザクションが失敗するかどうかの判断基準:@Transactionalアノテーションが付与されたメソッドを呼び出す際に、プロキシオブジェクトから直接呼び出されているかどうかを判断する必要があり、そうであればトランザクションが有効となり、そうでなければトランザクションが有効となる失敗。(追記:この点は見落とされやすいです)
さらに、次のような、 が@Bean
あるのとないの@Configuration
では結果が異なるという典型的な例もあります。
Bean を宣言する方法:
@ComponentScan("org.tuling.spring")
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService(walletService());
}
@Bean
public UserService userService1() {
return new UserService(walletService());
}
@Bean
public WalletService walletService() {
return new WalletService();
}
}
// UserService声明
public class UserService {
private WalletService walletService;
public UserService() {
}
public UserService(WalletService walletService) {
this.walletService = walletService;
}
public WalletService getWalletService() {
return walletService;
}
/**
* 自我介绍
*/
public void selfIntroduction() {
System.out.println("你好,我是阿通,我有好多钱");
walletService.showMyBalance();
}
}
WalletService
上記の Bean の宣言方法を見ると、シングルトンであることが前提なので、保持されているオブジェクトuserService
と同じである必要があります。userService1
walletService
呼び出し方法:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService)context.getBean("userService");
System.out.println(userService);
System.out.println(userService.getWalletService());
System.out.println("--------------------------------");
UserService userService1 = (UserService)context.getBean("userService1");
System.out.println(userService1);
System.out.println(userService1.getWalletService());
結果の出力は次のようになります。
org.tuling.spring.bean.UserService@2c34f934
org.tuling.spring.bean.WalletService@12d3a4e9
--------------------------------
org.tuling.spring.bean.UserService@240237d2
org.tuling.spring.bean.WalletService@12d3a4e9
結果を見てみると特に問題はなく、予定通りに出力されています。しかし、 Bean を宣言するメソッドを削除すると@Configuration
、結果は次のようになります。
@ComponentScan("org.tuling.spring")
//@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService(walletService());
}
@Bean
public UserService userService1() {
return new UserService(walletService());
}
@Bean
public WalletService walletService() {
return new WalletService();
}
}
org.tuling.spring.bean.UserService@710726a3
org.tuling.spring.bean.WalletService@646007f4
--------------------------------
org.tuling.spring.bean.UserService@481a15ff
org.tuling.spring.bean.WalletService@78186a70
単に@Configuration
結果に注釈を付けるだけでは異なるのはなぜでしょうか? 以下のように分析します。
@Bean
アノテーションはメソッドが返すオブジェクトをBeanとして登録することができ、BeanはSpringコンテナで管理されます。これ以上何もない- したがって、
userService()
メソッドが再度呼び出されるときwalletService()
、それは実際には単なる通常の Java 呼び出しであり、必ず再度呼び出されます。new WalletService()
- アノテーションが付けられた後
@Configuration
、すべてのメソッドがプロキシされます (ソースコードの証拠はまだ見つかっていないため、後で理解できたら添付します)
要約する
- Springの起動プロセスを簡単に学習しました
- 一連の一般的な Spring 操作を通じて、IOC と AOP の一般的なプロセスを一般的に理解しました。