記事ディレクトリ
春のデザインパターン
1. 制御の反転 (IoC) と依存関係の注入 (DI)
IoC (Inversion of Control、制御の反転) はSpring において非常に重要な概念であり、テクノロジーではなく、分離された設計アイデアです。IoC の主な目的は、「サードパーティ」(Spring では IoC コンテナ) を使用して、依存関係のあるオブジェクト (IOC コンテナ管理オブジェクト。そのまま使用できます) 間の分離を実現し、それによってコード間の結合を減らすことです。
IoC はパターンではなく原則であり、次のパターン (ただし、これらに限定されません) は IoC 原則を実装します。
Spring IoC コンテナはファクトリーのようなもので、オブジェクトを作成する必要がある場合、オブジェクトがどのように作成されるかを考えずに、設定ファイルやアノテーションを設定するだけで済みます。IoC コンテナは、オブジェクトの作成、それらの相互接続、これらのオブジェクトの構成、および作成から完全に破棄されるまでのこれらのオブジェクトのライフサイクル全体の処理を担当します。
実際のプロジェクトでは、Service クラスの最下層に数百、さらには数千のクラスがある場合、この Service をインスタンス化する必要があります。この Service のすべての最下層クラスのコンストラクターを毎回把握する必要がある場合があり、これによりドライブが発生する可能性があります。人々は狂っている。IOC を使用する場合、IOC を設定して必要な箇所で参照するだけで済むため、プロジェクトの保守性が大幅に向上し、開発の難易度が軽減されます。
Spring IOC の理解については、Zhihu でこの回答を読むことをお勧めします: https://www.zhihu.com/question/23277575/answer/169698662open in new window。これは非常に優れています。
制御の反転を理解するにはどうすればよいですか?例: 「オブジェクト a はオブジェクト b に依存しています。オブジェクト a がオブジェクト b を使用する必要がある場合、オブジェクト a はオブジェクト b を独自に作成する必要があります。しかし、IOC コンテナがシステムに導入されると、オブジェクト a とオブジェクト b の間にギャップが生じます。オブジェクト a とオブジェクト b。直接接続が失われます。この時点で、オブジェクト a がオブジェクト b を使用する必要がある場合、IOC コンテナを指定してオブジェクト b を作成し、それをオブジェクト a に注入できます。物体aが物体bに依存する過程が能動的な行動から受動的な行動に変化し、制御が逆転することが制御反転の名前の由来です。
DI(Dependency Inject、依存性注入)とは、制御の反転を実現するデザインパターンで、オブジェクトにインスタンス変数を渡すことを指します。
2. 工場設計パターン
Spring はファクトリ パターンを使用して、BeanFactory
またはを通じてApplicationContext
Bean オブジェクトを作成します。
2 つを比較してください:
BeanFactory
: 遅延注入 (特定の Bean が使用された場合にのみ注入されます) に比べてApplicationContext
メモリ占有量が少なく、プログラムの起動が速くなります。ApplicationContext
: コンテナーが開始されると、コンテナーを使用するかどうかに関係なく、すべての Bean が一度に作成されます。BeanFactory
最も基本的な依存関係注入サポートのみを提供しますが、ApplicationContext
拡張後はBeanFactory
、BeanFactory
いくつかの、追加の機能があるため、ほとんどの開発者ApplicationContext
はさらに使用することになります。
ApplicationContext
3 つの実装クラス:
ClassPathXmlApplication
: コンテキスト ファイルをクラスパス リソースとして扱います。FileSystemXmlApplication
: ファイル システム内の XML ファイルからコンテキスト定義情報を読み込みます。XmlWebApplicationContext
:XMLファイルからコンテキスト定義情報をWebシステムに読み込みます。
例:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
public class App {
public static void main(String[] args) {
ApplicationContext context = new FileSystemXmlApplicationContext(
"C:/work/IOC Containers/springframework.applicationcontext/src/main/resources/bean-factory-config.xml");
HelloApplicationContext obj = (HelloApplicationContext) context.getBean("helloApplicationContext");
obj.getMsg();
}
}
3. シングルトン設計パターン
実現方法
1. お腹を空かせた中華風
コード:
public class Test {
public static void main(String[] args) {
Single single = Single.getSingle();
}
}
class Single{
private Single(){
}
private static Single s = new Single();
public static Single getSingle(){
return s;
}
}
2. 怠け者のスタイル
遅延読み込みでは、実際に使用されるときにのみインスタンス化が開始されます。
スレッド セーフティの問題:
ダブル チェック ロック最適化
コンパイラ (JIT) により、CPU が命令を並べ替えることがあり、初期化されていないインスタンスが使用されることになりますが、これは volatile キーワードを追加することで変更できます。volatile で変更されたフィールドについては、命令が繰り返されるのを防ぐことができます。 . 行。
コード:
class Single {
// 禁止指令重排
private volatile static Single s;
private Single() {
}
public static Single getSingle() {
if (s == null) {
synchronized (Single.class) {
if (s == null) {
s = new Single();
// 字节码层
// JIT , CPU 有可能对如下指令进行重排序
// 1 .分配空间
// 2 .初始化
// 3 .引用赋值
// 如重排序后的结果为如
// 1 .分配空间
// 3 .引用赋值 如果在当前指令执行完,有其他线程来获取实例,将拿到尚未初始化好的实例
// 2 .初始化
}
}
}
return s;
}
}
2. 静的内部クラス
基本的に、クラス ロード メカニズムはスレッドの安全性を確保するために使用され、
クラスの初期化は実際に使用されるときにのみトリガーされるため、遅延ロードの一種でもあります。
コード:
class Single {
private static class InnerSingle {
private static Single s = new Single();
}
private Single() {
// 防止利用反射攻击
if(InnerSingle.s == null){
throw new RuntimeException("单例不允许多个实例");
}
}
public static Single getSingle() {
return InnerSingle.s;
}
}
4. 列挙型クラス
当然ながら、対応するインスタンスを作成するためのリフレクションはサポートされておらず、独自の逆シリアル化メカニズムがあり、
スレッドの安全性を確保するためにクラスローディングメカニズムが使用されます。
コード:
public enum EnumSingleton {
INSTANCE;
}
春のシングルトン パターン
私たちのシステムには、スレッド プール、キャッシュ、ダイアログ ボックス、レジストリ、ログ オブジェクト、プリンタやグラフィックス カードなどのデバイス ドライバとして機能するオブジェクトなど、実際には 1 つだけ必要なオブジェクトがいくつかあります。実際、このタイプのオブジェクトはインスタンスを 1 つしか持つことができず、複数のインスタンスを作成すると、プログラムの異常な動作、過剰なリソースの使用、一貫性のない結果などの問題が発生する可能性があります。
シングルトン パターンを使用する利点:
- 頻繁に使用されるオブジェクトの場合、オブジェクトの作成にかかる時間を省略できます。これは、これらの重量のあるオブジェクトにとって非常に大きなシステム オーバーヘッドとなります。
- 新しい操作の数が減るため、システム メモリの使用頻度も減り、GC の圧力が軽減され、GC の一時停止時間が短縮されます。
Spring の Bean のデフォルトのスコープはシングルトンです。シングルトン スコープに加えて、Spring の Bean には次のスコープもあります。
- プロトタイプ: 新しい Bean インスタンスが取得されるたびに作成されます。つまり、
getBean()
2 回連続して異なる Bean インスタンスを取得します。 - request (Web アプリケーションでのみ利用可能): 各 HTTP リクエストは、現在の HTTP リクエスト内でのみ有効な新しい Bean (リクエスト Bean) を生成します。
- セッション(Web アプリケーションでのみ利用可能): 新しいセッションからの各 HTTP リクエストは、現在の HTTP セッション内でのみ有効な新しい Bean (セッション Bean) を生成します。
- application/global-session (Web アプリケーションでのみ使用可能): 各 Web アプリケーションは起動時に Bean (アプリケーション Bean) を作成します。Bean は現在のアプリケーション起動時間中にのみ有効です。
- websocket (Web アプリケーションでのみ使用可能): 各 WebSocket セッションは新しい Bean を生成します。
Spring は、ConcurrentHashMap
シングルトン レジストリを実装する特別な方法を通じてシングルトン パターンを実装します。
Spring のシングルトン実装のコア コードは次のとおりです。
// 通过 ConcurrentHashMap(线程安全) 实现单例注册表
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "'beanName' must not be null");
synchronized (this.singletonObjects) {
// 检查缓存中是否存在实例
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//...省略了很多代码
try {
singletonObject = singletonFactory.getObject();
}
//...省略了很多代码
// 如果实例对象在不存在,我们注册到单例注册表中。
addSingleton(beanName, singletonObject);
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
//将对象添加到单例注册表
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
}
}
}
シングルトン Bean にスレッドの安全性の問題はありますか?
ほとんどの場合、プロジェクトではマルチスレッドを使用しないため、この問題に注目する人はほとんどいません。シングルトン Bean にはスレッドの問題があります。これは主に、複数のスレッドが同じオブジェクトを操作するときにリソースの競合が発生するためです。
一般的な解決策は 2 つあります。
- Bean 内で変更可能なメンバー変数を定義することは避けてください。
- クラスでメンバー変数を定義し
ThreadLocal
、必要な変数メンバー変数をThreadLocal
(推奨される方法) に保存します。
ただし、ほとんどの Bean は実際にはステートレス (インスタンス変数なし) (Dao、Service など) であり、この場合、Bean はスレッドセーフです。
4. 代理店の設計パターン
AOP でのプロキシ パターンの適用
AOP (アスペクト指向プログラミング) は、ビジネスとは関係がないものの、システム内の重複コードを減らすためにビジネス モジュールによって通常呼び出されるロジックや責任 (トランザクション処理、ログ管理、権限制御など) をカプセル化できます。 、モジュール間の結合を減らし、将来の拡張性と保守性を促進します。
Spring AOP は動的プロキシに基づいています。プロキシされるオブジェクトが特定のインターフェイスを実装している場合、Spring AOP はJDK プロキシを使用してプロキシ オブジェクトを作成します。インターフェイスを実装していないオブジェクトの場合、JDK プロキシをプロキシに使用することはできません。今回は、次の図に示すように、Spring AOP はCglibを使用して、プロキシされたオブジェクトのサブクラスをプロキシとして生成します。
もちろん、AspectJ を使用することもできます。Spring AOP には AspectJ が統合されています。AspectJ は、Java エコシステムで最も完全な AOP フレームワークと見なされるべきです。
AOP を使用すると、いくつかの一般的な関数を抽象化し、必要な場所で直接使用できるため、コードの量が大幅に簡素化されます。新しい機能を追加する必要がある場合にも便利で、システムの拡張性も向上します。AOP は、ログ機能、トランザクション管理、その他のシナリオで使用されます。
Spring AOP と AspectJ AOP の違いは何ですか?
Spring AOP は実行時の拡張機能ですが、AspectJ はコンパイル時の拡張機能です。Spring AOP はプロキシ処理に基づいていますが、AspectJ はバイトコード操作に基づいています。
Spring AOP には AspectJ が統合されています。これは、Java エコシステムで最も完全な AOP フレームワークとみなされます。AspectJ は Spring AOP より強力ですが、Spring AOP は比較的単純です。
アスペクトの数が少ない場合、2 つの間のパフォーマンスに大きな違いはありません。ただし、アスペクトが多すぎる場合は、Spring AOP よりもはるかに高速な AspectJ を選択するのが最善です。
5. テンプレートメソッド
テンプレート メソッド パターンは、一部のステップをサブクラスに延期しながら、動作中のアルゴリズムのスケルトンを定義する動作設計パターンです。テンプレート メソッドを使用すると、アルゴリズムの構造を変更せずに、サブクラスでアルゴリズムの特定のステップの実装を再定義できます。
public abstract class Template {
//这是我们的模板方法
public final void TemplateMethod(){
PrimitiveOperation1();
PrimitiveOperation2();
PrimitiveOperation3();
}
protected void PrimitiveOperation1(){
//当前类实现
}
//被子类实现的方法
protected abstract void PrimitiveOperation2();
protected abstract void PrimitiveOperation3();
}
public class TemplateImpl extends Template {
@Override
public void PrimitiveOperation2() {
//当前类实现
}
@Override
public void PrimitiveOperation3() {
//当前类实现
}
}
Spring ではJdbcTemplate
、HibernateTemplate
データベース上で動作する Template で終わるクラスはテンプレート パターンを使用します。通常、テンプレートパターンの実装には継承を使用しますが、Spring ではこの方法を使用せず、Callback パターンを使用してテンプレートメソッドパターンと連携することで、コードの再利用効果を実現するだけでなく、柔軟性も向上します。 . .
6. オブザーバーパターン
オブザーバーパターンはオブジェクトの動作パターンです。これはオブジェクト間の依存関係を表し、オブジェクトが変更されると、そのオブジェクトに依存するすべてのオブジェクトも反応します。Spring のイベント駆動モデルは、オブザーバー パターンの古典的なアプリケーションです。Spring イベント駆動モデルは非常に便利で、多くのシナリオでコードを分離できます。たとえば、製品を追加するたびに製品インデックスを更新する必要がありますが、このとき、オブザーバー パターンを使用すると、この問題を解決できます。
Spring イベント駆動モデルにおける 3 つの役割
イベントの役割
ApplicationEvent
(org.springframework.context
パッケージ配下) イベントとして動作し、インターフェースを継承しjava.util.EventObject
て実装する抽象クラスです。java.io.Serializable
次のイベントは Spring にデフォルトで存在し、すべてApplicationContextEvent
( から継承されたApplicationContextEvent
) の実装です。
ContextStartedEvent
:ApplicationContext
起動後にトリガーされるイベント。ContextStoppedEvent
:ApplicationContext
停止後にトリガーされるイベント。ContextRefreshedEvent
:ApplicationContext
初期化またはリフレッシュが完了した後にトリガーされるイベント。ContextClosedEvent
:ApplicationContext
閉店後に発生するイベント。
イベントリスナーの役割
ApplicationListener
onApplicationEvent()
イベント リスナーとして機能する、処理用のメソッドが 1 つだけ定義されたインターフェイスですApplicationEvent
。ApplicationListener
インターフェースクラスのソースコードは以下の通りで、インターフェースの定義とインターフェース内のイベントを実装するだけでわかりますApplicationEvent
。したがって、Spring では、リスニング イベントを完了するためにApplicationListener
インターフェイスのメソッドを実装するだけで済みます。onApplicationEvent()
package org.springframework.context;
import java.util.EventListener;
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E var1);
}
イベント発行者の役割
ApplicationEventPublisher
イベント発行者として機能し、インターフェイスでもあります。
@FunctionalInterface
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
this.publishEvent((Object)event);
}
void publishEvent(Object var1);
}
ApplicationEventPublisher
publishEvent()
このインターフェースのメソッドはクラスに実装されており、このメソッドの実装を読むと、実際にイベントがブロードキャストされているAbstractApplicationContext
ことがわかります。ApplicationEventMulticaster
春のイベントの流れまとめ
- イベントを定義します。継承されたイベントを実装し
ApplicationEvent
、対応するコンストラクターを作成します。 - イベント リスナーを定義します。
ApplicationListener
インターフェイスを実装し、onApplicationEvent()
メソッドをオーバーライドします。 ApplicationEventPublisher
イベント パブリッシャーを使用してメッセージを公開する:のメソッドを通じてメッセージを公開できますpublishEvent()
。
例:
// 定义一个事件,继承自ApplicationEvent并且写相应的构造函数
public class DemoEvent extends ApplicationEvent{
private static final long serialVersionUID = 1L;
private String message;
public DemoEvent(Object source,String message){
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
// 定义一个事件监听者,实现ApplicationListener接口,重写 onApplicationEvent() 方法;
@Component
public class DemoListener implements ApplicationListener<DemoEvent>{
//使用onApplicationEvent接收消息
@Override
public void onApplicationEvent(DemoEvent event) {
String msg = event.getMessage();
System.out.println("接收到的信息是:"+msg);
}
}
// 发布事件,可以通过ApplicationEventPublisher 的 publishEvent() 方法发布消息。
@Component
public class DemoPublisher {
@Autowired
ApplicationContext applicationContext;
public void publish(String message){
//发布事件
applicationContext.publishEvent(new DemoEvent(this, message));
}
}
たとえば のメソッドが呼び出されるとDemoPublisher
、コンソールには次のように出力されます。publish()
demoPublisher.publish("你好")
接收到的信息是:你好
7.アダプターモード
アダプター パターンは、あるインターフェイスをクライアントが必要とする別のインターフェイスに変換し、互換性のないインターフェイスを持つクラスが連携できるようにします。
Spring AOP のアダプター パターン
Spring AOP の実装はプロキシ モードに基づいていることはわかっていますが、Spring AOP の機能拡張やアドバイスではアダプター モードが使用されており、関連するインターフェイスは ですAdvisorAdapter
。
一般的に使用されるアドバイスのタイプは次のとおりです: BeforeAdvice
(ターゲット メソッドの呼び出し前、プレアドバイス)、AfterAdvice
(ターゲット メソッドの呼び出し後、ポストアドバイス)、AfterReturningAdvice
(ターゲット メソッドの実行後、リターン前) など。アドバイスの各タイプには、対応するインターセプターがあります: MethodBeforeAdviceInterceptor
、AfterReturningAdviceInterceptor
などThrowsAdviceInterceptor
。
Spring の事前定義された通知は、対応するアダプターを介してインターフェイス (メソッド インターセプター) タイプのオブジェクトに適応する必要がありますMethodInterceptor
(たとえば、メソッドをMethodBeforeAdviceAdapter
呼び出して に適応します)。getInterceptor
MethodBeforeAdvice
MethodBeforeAdviceInterceptor
Spring MVC のアダプター パターン
Spring MVCでは、DispatcherServlet
リクエスト情報に従って呼び出されHandlerMapping
、対応するリクエストを解析しますHandler
。対応するもの(通常コントローラーとHandler
呼ばれるもの) に解析した後、アダプターによる処理が開始されます。望ましいインターフェースとして、特定のアダプター実装クラスを使用して、ターゲット クラスを適応する必要があるクラスとして適応させます。Controller
HandlerAdapter
HandlerAdapter
Controller
Spring MVC でアダプター パターンを使用するのはなぜですか?
Spring MVC には多くの型がありController
、型が異なればController
リクエストは異なる方法で処理されます。アダプター モードを使用しない場合は、次のコードのように、DispatcherServlet
対応する型を直接取得しController
、必要なものを自分で判断できます。
if(mappedHandler.getHandler() instanceof MultiActionController){
((MultiActionController)mappedHandler.getHandler()).xxx
}else if(mappedHandler.getHandler() instanceof XXX){
...
}else if(...){
...
}
別の型を追加するとController
、上記のコードにさらに判定文を追加する必要があり、この形式ではプログラムの保守が困難になり、拡張にはオープン、変更にはクローズという設計パターンの開閉原則に違反します。
デコレータパターン
デコレーター パターンは、追加のプロパティまたは動作をオブジェクトに動的に追加できます。継承を使用する場合と比較して、デコレータ パターンはより柔軟です。簡単に言うと、元の関数を変更する必要があるが、元のコードを直接変更したくない場合、元のコードの外側に収まるように Decorator を設計します。実際、デコレータ パターンは JDK のInputStream
ファミリーなど多くの場所で使用されており、クラスの下にサブクラス(ファイルの読み取り)、(キャッシュの増加、ファイルの読み取り速度の大幅な向上) などがInputStream
あり、拡張することができます。コードを変更することなく、その機能を確認できます。FileInputStream
BufferedInputStream
InputStream
Spring で DataSource を構成する場合、DataSource は異なるデータベースやデータ ソースになる可能性があります。元のクラスコードの変更を少なくして、顧客のニーズに応じて異なるデータソースを動的に切り替えることができますか? このときデコレータパターンを使います(具体的な原理はまだ分かりません)。Spring で使用されるラッパー パターンには、クラス名にWrapper
または が含まれますDecorator
。これらのクラスは基本的に、オブジェクトにいくつかの追加の責任を動的に追加します。
要約する
Spring フレームワークではどのようなデザイン パターンが使用されていますか?
- ファクトリ デザイン パターン: Spring はファクトリ パターンを使用して Bean オブジェクトを
BeanFactory
作成しますApplicationContext
。 - プロキシ設計パターン: Spring AOP 機能の実装。
- シングルトン設計パターン: Spring の Bean はデフォルトでシングルトンです。
- テンプレートメソッドパターン: Spring では
jdbcTemplate
、hibernateTemplate
データベース上で動作する Template で終わるクラスはテンプレートパターンを使用します。 - ラッパー設計パターン: 私たちのプロジェクトは複数のデータベースに接続する必要があり、さまざまな顧客が訪問するたびにニーズに応じてさまざまなデータベースにアクセスします。このモデルにより、顧客のニーズに応じて異なるデータ ソースを動的に切り替えることができます。
- オブザーバー パターン: Spring のイベント駆動モデルは、オブザーバー パターンの古典的なアプリケーションです。
- アダプター パターン: Spring AOP の拡張またはアドバイスはアダプター パターンを使用し、Spring MVC もアダプター パターン アダプテーションを使用します
Controller
。 - …