[インタビューの質問] Spring IoCとDI、自動アセンブリと循環依存

Spring IoCはSpringの最も古典的な設計であり、自動アセンブリはIoC注入「自動化」の簡略化された構成操作です。IoCインジェクションはオブジェクト間の依存関係を管理するのに役立ちますが、不適切な設計が発生し、循環的な依存関係が発生する可能性があります。強力なSpringは、いくつかのエレガントなソリューションも提供します。

目次

IoCとは

IoCと制御の反転

IoCコンテナを理解する方法

IoC注入法

長所と短所の比較

自動組立

組立方法

循環依存

解決


IoCとは

Spring IoCコンテナは、オブジェクトのライフサイクルとオブジェクト間の(依存関係)関係を担当します。
新しいBeanを作成するとき、IoCコンテナーは、手動で作成するのではなく、新しいBeanが依存する他のBeanを自動的に挿入します。
ここに写真の説明を書いてください

  • IoCコンテナは、オブジェクトの初期化を自動的に完了し、開発プロセス中に初期化コードの大部分を記述しないようにします。
  • インスタンスを作成するときに詳細を知る必要はありません。

利点:
ビジネスコードにあまり邪魔にならず、オブジェクトのテスト容易性、再利用性、およびスケーラビリティが向上します。

利点は非常に抽象的なように見え、例と併せて理解できます。WangFuqiangの「春の秘密」セクション2.3IoCの付加価値を参照してください。

IoCと制御の反転

IoCのフルネームはInversionofControlであり、これは「制御の反転」と解釈されます。

  1. 誰が誰を制御するか:従来の開発モデルでは、オブジェクトを直接作成することでオブジェクトを作成します。つまり、依存するオブジェクトは自分で直接制御しますが、IOCコンテナを作成した後は、IoCコンテナによって直接制御されます。制御する。したがって、「誰が誰を制御するか」は、もちろんIoCコンテナによる制御の対象です。
  2. 何をするために制御します。コントロールオブジェクトを。
  3. 逆転である理由:IoCがない場合、私たちは皆、自分のオブジェクトに依存オブジェクトを積極的に作成します。これは順方向の回転です。ただし、IoCを使用すると、依存オブジェクトはIoCコンテナによって直接作成され、注入されたオブジェクトに注入されます。依存オブジェクトは、アクティブに取得されるものからパッシブに受け入れられるものに変わるため、その逆になります。
  4. 逆転する側面:依存オブジェクトの取得が逆転します。

IoCにはDI(依存関係インジェクション)と呼ばれるエイリアス、つまり依存関係インジェクションもあると一般に考えられています(エラー、以下で説明します)。「依存関係の注入」は、「注入されたオブジェクトがIoCコンテナ構成の依存オブジェクトに依存する」ことを明確に説明しています。
DIを理解するための鍵は、「誰が誰に依存するのか、なぜ依存する必要があるのか​​、誰が誰を注入するのか、そして何が注入されるのか」です。次に、詳細に分析しましょう。

  1. 誰が誰に依存するか:「注入されたオブジェクト」は「依存オブジェクト」に依存します。たとえば、オブジェクトAがBに依存している場合、IoCコンテナはオブジェクトAを注入する前にオブジェクトBを注入する必要があります。オブジェクトAはIoCコンテナに依存します。オブジェクトBはオブジェクトAに注入されるため、Aは注入されたオブジェクトであり、Bは依存しています。オブジェクト、AはBに依存します。
  2. 依存する必要がある理由:コンテナ管理オブジェクトには、オブジェクトに必要な外部リソースを提供するためのIoCコンテナが必要です。
  3. 誰が誰を注入するか:明らかに、IoCコンテナはオブジェクトを注入します。つまり、「依存オブジェクト」を注入します。
  4. 何が注入される:それはオブジェクトによって必要とされる(オブジェクト、リソース、定数データを含む)外部リソースを注入することです。

2つ
の制御の反転(制御の反転)の関係は、反転の原則に依存するコード設計のアイデアです。使用される特定の方法は、いわゆる依存性注入です。

IoCコンテナを理解する方法

自分でcarインスタンスを手動で作成する場合は、下から上に向かって新しく開始し
ここに写真の説明を書いてください
ます。各クラスのコンストラクターは、基になるコードのコンストラクターを直接呼び出します。このプロセスでは、Car / Framework / Bottom / Tireクラスのコンストラクター全体がどのように定義されているかを理解して、段階的に新規/注入する必要があります。最下層の変更は、上層のすべての変更に影響します。クラスを変更するたびに、それに依存するすべてのクラスを変更する必要があり、ソフトウェアのメンテナンスコストが高すぎます。

IoC Containerがこの作業を行う場合、その逆であり、最上位レベルから開始して依存関係を見つけるために下降し、最下位レベルに到達した後、次のステップに進みます(深度優先トラバーサルのようなものです)。いわゆる依存関係注入は、下位クラスをパラメータとして上位クラスに渡し、上位クラスの「制御」を下位クラスに実現することです。
ここに写真の説明を書いてください
ここで、IoCコンテナは、特定のインスタンス作成の詳細を直接非表示にすることができます。私たちの見解では、それは工場のようなものです。私たち
ここに写真の説明を書いてください
は工場の顧客のようなものです。工場からCarインスタンスをリクエストするだけで、Configに従ってCarインスタンスが作成されます。このCarインスタンスがどのように段階的に作成されるかは気にしません。

IoC注入法

  • コンストラクター注入
    注入されたオブジェクトのコンストラクターメソッドのパラメーターリストは、依存オブジェクトのパラメーターリストを宣言します
private DependencyA dependencyA;
private DependencyB dependencyB;
private DependencyC dependencyC;

@Autowired
public DI(DependencyA dependencyA, DependencyB dependencyB, DependencyC dependencyC) {
    this.dependencyA = dependencyA;
    this.dependencyB = dependencyB;
    this.dependencyC = dependencyC;
}

Spring 4.3以降では、コンストラクタインジェクションは非表示インジェクションメソッドをサポートしています。

private DependencyA dependencyA;
private DependencyB dependencyB;
private DependencyC dependencyC;

// @Autowired
public DI(DependencyA dependencyA, DependencyB dependencyB, DependencyC dependencyC) {
    this.dependencyA = dependencyA;
    this.dependencyB = dependencyB;
    this.dependencyC = dependencyC;
}
  • セッターメソッドインジェクションインジェクション
    されたオブジェクトのsetter()メソッドのパラメータリスト、依存オブジェクトを宣言します
private DependencyA dependencyA;
private DependencyB dependencyB;
private DependencyC dependencyC;

@Autowired
public void setDependencyA(DependencyA dependencyA) {
    this.dependencyA = dependencyA;
}

@Autowired
public void setDependencyB(DependencyB dependencyB) {
    this.dependencyB = dependencyB;
}

@Autowired
public void setDependencyC(DependencyC dependencyC) {
    this.dependencyC = dependencyC;
}
  • インターフェイスインジェクション
    は面倒です。めったに使用されていません。
    注入されたオブジェクトによって実装されたインターフェイスメソッドのパラメータリストは、依存オブジェクトを宣言します
  • フィールドインジェクション
@Autowired
private DependencyA dependencyA;

@Autowired
private DependencyB dependencyB;

@Autowired
private DependencyC dependencyC;

長所と短所の比較

注入法 利点 不利益 実用シーン
フィールドインジェクション シンプルで、新しい依存関係を簡単に追加できます インジェクションの失敗とNullPointedExceptionが発生する可能性があります。テストおよびその他のモジュールでは使用できません。最終フィールドには使用できないため、フィールドの不変性は保証されません。 春の関係者は、この書き方を推奨していません。
セッターインジェクション 柔軟性が高く、依存オブジェクトを簡単に変更できます セッターインジェクションのみを使用する依存オブジェクトには、null以外のチェックが必要です。オブジェクトは、構築が完了した直後に準備完了状態に入ることができません。 デフォルト以外の値で依存オブジェクトを再注入します(コンストラクター注入);必須でない依存関係の場合は、セッター注入をお勧めします。
コンストラクターインジェクション オブジェクトが作成されると、準備完了状態になり、すぐに使用できます。 従属オブジェクトが多いと、構築方法のパラメータリストが長くなり、保守や使用が面倒になります。単一責任の原則として、この時点で再構築を検討する必要があります。不注意による使用も循環依存を引き起こす可能性があります。  

Spring 4.x以降、必要に応じて、インジェクションメソッドをセッターまたはコンストラクターインジェクションにする必要があります。

当局がフィールドインジェクションを推奨しないのはなぜですか

  1. 単一責任の侵入
    依存関係の追加は非常に単純で、おそらく単純すぎます。6、10、または多数の依存関係を追加することはまったく難しくなく、単一責任の原則に違反するプログラムを検出することは困難です。
    コンストラクターインジェクションメソッドを使用する場合、単一責任の原則に違反するプログラムを見つけるのは比較的簡単です。コンストラクターメソッドを使用して注入すると、ある時点で、コンストラクターのパラメーターが非常に多くなり、何かが間違っていることが明らかになります。依存関係が多すぎるということは、通常、クラスの責任が大きいことを意味します。明らかに単一責任の原則(SRP:単一責任の原則)に違反しています。
  2. 変更されていないフィールドは宣言できません。
    フィールドインジェクションは最終フィールドを注入できません。コンストラクタインジェクションのみが最終フィールドを注入できます。
  3. 非表示の依存関係。
    依存関係インジェクションコンテナを使用すると、クラスは依存オブジェクトを担当しなくなります。依存オブジェクトを取得する責任はクラスから削除され、IoCコンテナはクラスの組み立てに役立ちます。クラスが依存オブジェクトを担当しなくなった場合は、パブリックインターフェイスメソッドまたはコンストラクターをより明確に使用する必要があります。これにより、クラスに必要なものと、それがオプション(セッターインジェクション)か必須かを明確に理解できます。 (コンストラクター注入)。
  4. 依存関係インジェクションコンテナの緊密な結合
    依存関係インジェクションフレームワークのコアアイデアの1つは、コンテナによって管理されるクラスが、コンテナによって使用される依存オブジェクトに依存してはならないということです。言い換えれば、このクラスをすることができ、単純なPOJO(プレーン普通のJavaオブジェクト)である必要があり、別々にインスタンス化し、あなたもそれが必要依存関係とそれを提供することができますこの方法でのみ、テスト分離を実現するために依存関係注入コンテナーを開始しなくても、ユニットテストでこのクラスをインスタンス化できます(コンテナーの開始は、より統合テストです)。
    ただし、可変直接注入を使用する場合、このクラスを直接インスタンス化してそのすべての依存関係を満たす方法はありません。つまり、依存オブジェクトは手動で新規にする必要があるか、IoCコンテナのスコープ内でのみ使用できます。

自動組立

自動アセンブリは、依存関係を「自動化」に挿入するための簡略化された構成操作です。
オブジェクトのプロパティが別のオブジェクトである場合、インスタンス化するときにオブジェクトプロパティに対してインスタンス化する必要があります。これがアセンブリです。
オブジェクトがインターフェイスを介してのみ依存関係を表現する場合、この依存関係は、オブジェクト自体が認識していなくても、別の特定の実装に切り替えることができます。ただし、これには問題があります。従来の依存関係インジェクション構成では、属性をアセンブルするBean参照を明確にする必要があります。Beanが多くなると、保守が困難になります。このシナリオに基づいて、Springは自動アセンブリの注釈を使用してこの問題を解決します。自動配線とは、開発者がどのBean参照を組み立てるかを知る必要がないことを意味し、識別作業は春までに行われます。自動アセンブリと組み合わせて、「自動検出」があります。これは、アセンブリ用のBeanとして構成する必要があるクラスを自動的に認識します。このように、自動アセンブリは、依存関係の挿入を「自動化」するための簡略化された構成操作であることがわかります。

組立方法

  • ByNameは、属性と同じ名前のBeanをアセンブルします。
  • ByTypeは、属性と同じタイプのBeanをアセンブルします。-コンストラクターは、コンストラクターを介して同じタイプとパラメーターのBeanをアセンブルすることです。
  • 自動検出は、コンストラクターとbyTypeの組み合わせです。コンストラクターが最初に実行され、失敗した場合はbyTypeが実行されます。

どのアセンブリ方法を選択するかについては、ラベルのautowire属性を構成する必要があります。構成されていない場合、デフォルトはbyNameタイプであり、属性の名前に従って自動的にアセンブルされます。上記で最も一般的に使用されるのは、byNameとbyTypeです。自動組み立て中、組み立てられたBeanは、属性に一致する唯一のBeanである必要があり、それ以上でもそれ以下でもありません。また、組み立て可能なBeanは1つだけが自動的に正常に組み立てられます。それ以外の場合は、例外がスローされます。すべてのBeanの自動アセンブリタイプを統一する場合は、ラベルでdefault-autowire属性を構成できます。もちろん、autowire属性が構成されている場合でも、属性を手動でアセンブルすることができ、手動アセンブリは自動アセンブリをオーバーライドします。

春2.5以降、注釈の自動アセンブリが提供されます。ただし、これらの注釈を使用するには、構成ファイルでそれらを構成する必要があります<context:annotation-config />この構成でのみ、自動アセンブリに注釈を使用でき、注釈ベースのアセンブリはデフォルトで無効になっています。
一般的に使用される自動アセンブリ注釈は次のとおりです:@ Autowired、@ Qualifier(@ Resource、@ Inject、@ NamedはJavaEE標準であり、推奨されません)。
@AutowiredアノテーションはbyTypeタイプです。このアノテーションは、プロパティ、セッター、およびコンストラクターで使用できます。この注釈を使用する場合、クラスのプロパティにセッターメソッドを追加する必要はありません。ただし、この属性は必須であるため、アセンブルする必要があります。適切なBeanをアセンブルできない場合は、例外がスローされます。`NoSuchBeanDefinitionException、required = falseの場合、例外はスローされません。別の状況では、同じタイプのBeanが同時に複数あり、この例外もスローされます。このとき、どのBeanをアセンブルするかをさらに明確にする必要があります。このとき、@ QualifierアノテーションをBean名の値と組み合わせることができます。@QualifierアノテーションはアセンブリbyNameを使用するため、同じタイプの複数のBean間でアセンブリに使用される同じ名前のBeanを指定できます。@Qualifierアノテーションは、自動アセンブリ候補Beanの範囲を狭める役割を果たします。

構成の自動検出も、springmvcの最も強力な機能です。構成<context:component-scan base-package="">または注釈@ComponentScan("")
である限り、base-package属性は、自動的に検出およびスキャンされるパッケージを指定します。
この構成では、指定されたパッケージとそのサブパッケージの下でステレオタイプ注釈でマークされたクラスが自動的にスキャンされ、これらのクラスがSpring Beanとして登録されるため、構成ファイルでBeanタグとして1つずつ構成する必要はありません。ステレオタイプの注釈には、@ Controller、@ Components、@ Service、@ Repository、および@Componentでマークされたカスタム注釈が含まれます。生成されたBeanのIDは、デフォルトでクラスの非修飾名になります。つまり、クラス名の最初の文字が小文字に変更されます。@Controller( "helloworld")など、これらの注釈の値にBeanIDの値を書き込むことができます。あなたがスキャンされたパッケージの範囲を絞り込むしたい場合は、使用することができる<context:include-filter><context:exclude-filter>具体的な使用方法については、ここでは詳しく説明しません。スキャンされないクラスはBeanとして登録できず、他のクラスをアセンブルするために使用できないことに注意してください。したがって、この構成の基本パッケージの範囲は非常に重要です。

循環依存

依存関係の挿入に注意を払わない場合、循環依存関係があります。Bean
依存関係の順序:BeanA-> BeanB-> BeanA
例:

@Component
public class CircularDependencyA {
 
    private CircularDependencyB circB;
 
    @Autowired
    public CircularDependencyA(CircularDependencyB circB) {
        this.circB = circB;
    }
}
@Component
public class CircularDependencyB {
 
    private CircularDependencyA circA;
 
    @Autowired
    public CircularDependencyB(CircularDependencyA circA) {
        this.circA = circA;
    }
}

SpringBootApplicationを実行します。

@SpringBootApplication
@ComponentScan("com.example.circulardependency.constructor")
public class CirculardependencyApplication {

    public static void main(String[] args) {
        SpringApplication.run(CirculardependencyApplication.class, args);
    }
}

次のエラーが報告されます。

BeanCurrentlyInCreationException: Error creating bean with name 'circularDependencyA': Requested bean is currently in creation: Is there an unresolvable circular reference?

依存関係の円も描画します。
ここに写真の説明を書いてください

解決

循環依存は設計上の問題が原因で発生しますこれに対処する最善の方法は、再設計することです

https://zhuanlan.zhihu.com/p/84267654

実際の開発では、転覆が許可されないことが多いため、いくつかの解決策があります。

1.セッターインジェクションに切り替えます(推奨)

コンストラクターインジェクションとsetter異なり、オンデマンドでインジェクションされ、依存オブジェクトをnullにすることができます

@Component
public class CircularDependencyA {
 
    private CircularDependencyB circB;
 
    @Autowired
    public void setCircB(CircularDependencyB circB) {
        this.circB = circB;
    }
 
    public CircularDependencyB getCircB() {
        return circB;
    }
}
@Component
public class CircularDependencyB {
 
    private CircularDependencyA circA;
 
    private String message = "Hi!";
 
    @Autowired
    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }
 
    public String getMessage() {
        return message;
    }
}

ユニットテストを追加します。

@RunWith(SpringRunner.class)
@SpringBootTest
@ComponentScan("com.example.circulardependency.setter")
public class CirculardependencyApplicationTests {
    @Bean
    public CircularDependencyB getDependencyB() {
        return new CircularDependencyB();
    }

    @Bean
    public CircularDependencyA getDependencyA() {
        CircularDependencyA circularDependencyA = new CircularDependencyA();
        circularDependencyA.setCircularDependencyB(getDependencyB());
        return circularDependencyA;
    }

    @Test
    public void contextLoads() {
        System.out.println("Hello world.");
        CircularDependencyA circularDependencyA = getDependencyA();
        System.out.println(circularDependencyA.getCircularDependencyB().getMessage());
    }
}

フィールドアノテーションを使用すると循環依存関係を解決することもできますが、フィールドアノテーションは非公式の推奨プラクティスであるため、ここでは例を示しません。

2. @ Lazyアノテーション

@Lazy初期化の遅延。この例では、CircularDependencyAが最初に構築され、次にCircularDependencyBが構築されて依存関係の輪が壊れます。

@Component
public class CircularDependencyA {
 
    private CircularDependencyB circB;
 
    @Autowired
    public CircularDependencyA(@Lazy CircularDependencyB circB) {
        this.circB = circB;
    }
}

3. UsingApplicationContextAware、InitializingBean

ApplicationContextAwareは、BeanをロードするためにSpringContextを取得します。InitializingBeanは、Beanのプロパティを設定した後のアクションを定義します。

@Component
public class CircularDependencyA implements InitializingBean, ApplicationContextAware {
    private CircularDependencyB circB;
    private ApplicationContext context;
    
    @Override
    public void afterPropertiesSet() throws Exception {
        this.circB = context.getBean(CircularDependencyB.class);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }

    public CircularDependencyB getCircularDependencyB() {
        return circB;
    }
}

原則:Spring Beanのライフサイクルに関連しています。最初にインスタンス化し、次にsetApplicationContext()、afterPropertiesSet()を呼び出します。したがって、CircularDependencyBがロードされた後、CircularDependencyAをロードします。
の場合、CircularDependencyAが最初にインスタンス化され、afterPropertiesSet()が呼び出されたときに、CircularDependencyBがロードされていないことがわかります。最初に、CircularDependencyBがロードされ(CircularDependencyAはこの時点でインスタンス化されているため、スムーズにロードできるため、コンストラクターが呼び出されます)、プロパティcircBが設定され、CircularDependencyAのロードが完了します。

オリジナル:https//blog.csdn.net/programmer_at/article/details/82389221
[1] IoC注入方法:https:  //www.vojtechruzicka.com/field-dependency-injection-considered-harmful/
[2]ループ依存関係:https:  //www.baeldung.com/circular-dependencies-in-spring
[3]フィールドインジェクション:https:  //blog.marcnuri.com/field-injection-is-not-recommended/
[4] IoCおよびDI:概念の正確な説明バージョン:http
//sishuok.com/forum/blogPost/list/2427.htmlわかりやすいバージョン:https//www.zhihu.com/question/23277575/answer/169698662

史上最強のTomcat8パフォーマンス最適化

なぜアリババは90秒で100億に抵抗できるのですか?-サーバー側の高同時分散アーキテクチャの進化

B2Beコマースプラットフォーム--ChinaPayUnionPay電子決済機能

Zookeeperの分散ロックを学び、インタビュアーに感心してあなたを見てもらいましょう

SpringCloudeコマーススパイクマイクロサービス-Redisson分散ロックソリューション

もっと良い記事をチェックして、公式アカウントを入力してください-私にお願いします-過去に素晴らしい

深くソウルフルなパブリックアカウント0.0

おすすめ

転載: blog.csdn.net/a1036645146/article/details/109504106