Spring の ApplicationContext.getBean はなぜ悪いと考えられるのでしょうか?

StackOverflow https://stackoverflow.com/questions/812415

  •  03-07-2019
  •  | 
  •  

質問

私は春の一般的な質問をしました。 自動キャスト Spring Bean 複数の人が Spring に電話をかけたと回答しました。 ApplicationContext.getBean() できる限り避けるべきです。何故ですか?

Spring で作成するように設定した Bean にアクセスするには、他にどのようにすればよいでしょうか?

私は非 Web アプリケーションで Spring を使用しており、共有アプリケーションにアクセスすることを計画していました。 ApplicationContext 物体 LiorH の説明によると.

修正

私は以下の回答を受け入れますが、マーティン・ファウラーによる別の見解を以下に示します。 依存性注入と依存性注入のメリットについて説明します。サービスロケーターの使用 (これはラップされたメソッドを呼び出すことと本質的に同じです) ApplicationContext.getBean()).

ファウラー氏は部分的に次のように述べている。サービス ロケーターを使用すると、アプリケーション クラスはロケーターへのメッセージによって明示的にサービスを要求します。インジェクションでは明示的なリクエストはなく、サービスはアプリケーション クラスに表示されるため、制御が反転します。制御の反転はフレームワークの一般的な機能ですが、代償が伴います。理解するのが難しく、デバッグ時に問題が発生する傾向があります。したがって、全体として、私は必要がない限り、[制御の反転] を避けることを好みます。これはそれが悪いと言っているのではなく、より単純な代替案よりもそれ自体を正当化する必要があると考えているだけです。"

役に立ちましたか?

解決

他の質問のコメントでこれについて言及しましたが、Inversion of Controlの全体的なアイデアは、どのクラスにも依存するオブジェクトを取得する方法を誰も知らないか、気にしないことです。これにより、指定した依存関係の実装の種類をいつでも簡単に変更できます。また、依存関係の模擬実装を提供できるため、クラスのテストが容易になります。最後に、クラスをより単純にし、コアの責任により焦点を合わせます。

ApplicationContext.getBean()を呼び出すことは、制御の反転ではありません!特定のBean名に対してどの実装を構成するかを変更することは依然として簡単ですが、クラスは依存関係を提供するためにSpringに直接依存するようになり、他の方法では取得できません。テストクラスで独自のモック実装を作成して、それを自分で渡すことはできません。これは基本的に、依存性注入コンテナーとしてのSpringの目的を無効にします。

言いたいところはどこでも:

MyClass myClass = applicationContext.getBean("myClass");

代わりに、たとえば、メソッドを宣言する必要があります:

public void setMyClass(MyClass myClass) {
   this.myClass = myClass;
}

そしてあなたの設定:

<bean id="myClass" class="MyClass">...</bean>

<bean id="myOtherClass" class="MyOtherClass">
   <property name="myClass" ref="myClass"/>
</bean>

Springは自動的に myClass myOtherClass に挿入します。

この方法ですべてを宣言すると、そのルートには次のようなものがあります。

<bean id="myApplication" class="MyApplication">
   <property name="myCentralClass" ref="myCentralClass"/>
   <property name="myOtherCentralClass" ref="myOtherCentralClass"/>
</bean>

MyApplication は最も中心的なクラスであり、少なくとも間接的にプログラムの他のすべてのサービスに依存します。ブートストラップ時に、 main メソッドで applicationContext.getBean(&quot; myApplication&quot;)を呼び出すことができますが、 getBean()他の場所!

他のヒント

Inversion of Control(IoC)よりもService Locatorを好む理由は次のとおりです。

  1. Service Locatorは、コード内で他の人がフォローするのがずっと簡単です。 IoCは「魔法」ですが、メンテナンスプログラマーは、複雑なSpring構成と無数の場所をすべて理解して、オブジェクトの配線方法を把握する必要があります。

  2. IoCは、構成の問題をデバッグするにはひどいものです。特定のクラスのアプリケーションでは、設定が間違っているとアプリケーションが起動せず、デバッガーで行われていることをステップスルーする機会が得られない場合があります。

  3. IoCは、主にXMLベースです(アノテーションは物事を改善しますが、まだ多くのXMLがあります)。つまり、開発者は、Springで定義されているすべてのマジックタグを知らない限り、プログラムで作業できません。 Javaを知るだけでは十分ではありません。これにより、プログラマーの経験が少なくなります(つまり、Service Locatorなどのより単純なソリューションが同じ要件を満たす場合、実際にはより複雑なソリューションを使用するのは設計が貧弱です)。さらに、XMLの問題の診断のサポートは、Javaの問題のサポートよりもはるかに弱いです。

  4. 依存性注入は、より大きなプログラムにより適しています。ほとんどの場合、追加の複雑さはそれだけの価値はありません。

  5. 「実装を後で変更したい場合」に「Spring」がよく使用されます。 Spring IoCの複雑さなしにこれを達成する方法は他にもあります。

  6. Webアプリケーション(Java EE WAR)の場合、Springコンテキストはコンパイル時に効果的にバインドされます(オペレーターが爆発した戦争でコンテキストを操作したくない場合を除く)。 Springでプロパティファイルを使用することはできますが、サーブレットの場合、プロパティファイルは事前に決められた場所にある必要があります。つまり、同じボックスに複数のサーブレットを同時にデプロイすることはできません。 SpringをJNDIで使用して、サーブレットの起動時にプロパティを変更できますが、管理者が変更可能なパラメーターにJNDIを使用している場合、Spring自体の必要性は減ります(JNDIは実質的にサービスロケーターであるため)。

  7. Springでは、Springがメソッドにディスパッチされている場合、プログラムの制御を失う可能性があります。これは便利で、多くの種類のアプリケーションで機能しますが、すべてではありません。初期化中にタスク(スレッドなど)を作成する必要がある場合、またはコンテンツがWARにバインドされたときにSpringが認識していなかった変更可能なリソースが必要な場合は、プログラムフローを制御する必要があります。

Springはトランザクション管理に非常に優れており、いくつかの利点があります。 IoCは多くの状況で過剰なエンジニアリングを行い、メンテナーに不当な複雑さをもたらす可能性があるだけです。最初に使用しない方法を考えずに、IoCを自動的に使用しないでください。

application-context.xmlにクラスを含めると、getBeanを使用する必要がなくなります。ただし、それでも実際には不要です。スタンドアロンアプリケーションを作成していて、application-context.xmlにドライバークラスを含めたくない場合は、次のコードを使用して、Springにドライバーの依存関係を自動配線させることができます。

public class AutowireThisDriver {

    private MySpringBean mySpringBean;    

    public static void main(String[] args) {
       AutowireThisDriver atd = new AutowireThisDriver(); //get instance

       ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
                  "/WEB-INF/applicationContext.xml"); //get Spring context 

       //the magic: auto-wire the instance with all its dependencies:
       ctx.getAutowireCapableBeanFactory().autowireBeanProperties(atd,
                  AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);        

       // code that uses mySpringBean ...
       mySpringBean.doStuff() // no need to instantiate - thanks to Spring
    }

    public void setMySpringBean(MySpringBean bean) {
       this.mySpringBean = bean;    
    }
}

アプリの一部を使用する必要がある(テストなど)何らかのスタンドアロンクラスがある場合、これを数回行う必要がありましたが、アプリケーションコンテキストに含めたくないので実際にはアプリの一部ではありません。また、これにより、文字列名を使用してBeanをルックアップする必要がなくなります。

Springのようなものを使用することの最もクールな利点の1つは、オブジェクトを一緒に配線する必要がないことです。 Zeusの頭が開き、クラスが表示されます。必要に応じて、すべての依存関係が作成および配線されて完全に形成されています。それは魔法で幻想的です。

ClassINeed classINeed =(ClassINeed)ApplicationContext.getBean(&quot; classINeed&quot;); と言うほど、魔法は少なくなります。ほとんどの場合、コードが少ないほど優れています。あなたのクラスが本当にClassINeed Beanを必要としているのなら、なぜそれを単に配線しないのですか?

そうは言っても、明らかに最初のオブジェクトを作成する必要があります。 getBean()を介してBeanを取得するメインメソッドには何も問題はありませんが、使用するときは常にSpringのすべての魔法を使用しているわけではないため、回避する必要があります。

動機は、Springに明示的に依存しないコードを書くことです。そうすれば、コンテナを切り替えることを選択した場合、コードを書き直す必要はありません。

コンテナは、コードからは見えないものであり、要求されることなく魔法のようにニーズを満たしていると考えてください。

依存性注入は、「サービスロケーター」に対する対抗策です。パターン。名前で依存関係を検索する場合は、DIコンテナーを削除してJNDIなどを使用することもできます。

@Autowired または ApplicationContext.getBean()を使用することも同じです。両方の方法で、コンテキストで設定されたBeanを取得し、両方の方法でコードがspringに依存します。 避けるべき唯一のことは、ApplicationContextをインスタンス化することです。これを一度だけ実行してください!つまり、次のような行

ApplicationContext context = new ClassPathXmlApplicationContext("AppContext.xml");

アプリケーションで一度だけ使用する必要があります。

アイデアは、依存性注入(制御の反転、またはIoC)に依存することです。 。つまり、コンポーネントは必要なコンポーネントで構成されます。これらの依存関係は(コンストラクターまたはセッターを介して)注入されます-自分では取得できません。

ApplicationContext.getBean()では、コンポーネント内でBeanに明示的に名前を付ける必要があります。代わりに、IoCを使用することにより、構成で使用するコンポーネントを決定できます。

これにより、アプリケーションをさまざまなコンポーネント実装と簡単に再接続したり、モックされたバリアントを提供することでテスト用のオブジェクトを簡単に構成したりできます(たとえば、テスト中にデータベースにアクセスしないようにモックされたDAO)

他の人は一般的な問題を指摘しています(そして有効な答えです)が、追加のコメントを1つだけ提供します:絶対にそれを行うべきではなく、できるだけ少なくすることです。

通常これは、ブートストラップ中に一度だけ実行されることを意味します。そして、「ルート」にアクセスするだけです。 Bean。これにより、他の依存関係を解決できます。これは、ベースサーブレットのような再利用可能なコードにすることができます(Webアプリを開発する場合)。

Springの前提の1つは、カップリングを避けることです。インターフェイス、DI、AOPを定義して使用し、ApplicationContext.getBean()の使用を避けます:-)

理由の1つは、テスト容易性です。このクラスがあるとします:

interface HttpLoader {
    String load(String url);
}
interface StringOutput {
    void print(String txt);
}
@Component
class MyBean {
    @Autowired
    MyBean(HttpLoader loader, StringOutput out) {
        out.print(loader.load("http://stackoverflow.com"));
    }
}

このBeanをテストするにはどうすればよいですか?例えば。このように:

class MyBeanTest {
    public void creatingMyBean_writesStackoverflowPageToOutput() {
        // setup
        String stackOverflowHtml = "dummy";
        StringBuilder result = new StringBuilder();

        // execution
        new MyBean(Collections.singletonMap("https://stackoverflow.com", stackOverflowHtml)::get, result::append);

        // evaluation
        assertEquals(result.toString(), stackOverflowHtml);
    }
}

簡単ですか?

Springに依存している間(注釈のため)、コードを変更せずにSpringへの依存を削除できます(注釈定義のみ)。とにかく、しかし、それは春がすることとは別にコードをレビューしテストすることを可能にします。

ApplicationContextを使用する場合でも、同じことを行うことができます。ただし、巨大なインターフェイスである ApplicationContext をモックする必要があります。ダミーの実装が必要か、Mockitoなどのモックフレームワークを使用できます。

@Component
class MyBean {
    @Autowired
    MyBean(ApplicationContext context) {
        HttpLoader loader = context.getBean(HttpLoader.class);
        StringOutput out = context.getBean(StringOutput.class);

        out.print(loader.load("http://stackoverflow.com"));
    }
}
class MyBeanTest {
    public void creatingMyBean_writesStackoverflowPageToOutput() {
        // setup
        String stackOverflowHtml = "dummy";
        StringBuilder result = new StringBuilder();
        ApplicationContext context = Mockito.mock(ApplicationContext.class);
        Mockito.when(context.getBean(HttpLoader.class))
            .thenReturn(Collections.singletonMap("https://stackoverflow.com", stackOverflowHtml)::get);
        Mockito.when(context.getBean(StringOutput.class)).thenReturn(result::append);

        // execution
        new MyBean(context);

        // evaluation
        assertEquals(result.toString(), stackOverflowHtml);
    }
}

これはかなり可能性がありますが、ほとんどの人は最初のオプションがよりエレガントでテストが簡単になることに同意すると思います。

実際に問題となる唯一のオプションはこれです:

@Component
class MyBean {
    @Autowired
    MyBean(StringOutput out) {
        out.print(new HttpLoader().load("http://stackoverflow.com"));
    }
}

これをテストするには多大な労力が必要です。さもないと、Beanは各テストでstackoverflowへの接続を試みます。また、ネットワーク障害が発生すると(または、過度のアクセス率のためにstackoverflowの管理者がブロックすると)、ランダムにテストが失敗します。

結論として、 ApplicationContext を直接使用することは自動的に間違っているとは言いません。ただし、より優れたオプションがある場合(およびほとんどの場合)、より優れたオプションを使用してください。

getBean() が必要となる状況は 2 つだけ見つかりました。

他の人は、スタンドアロン プログラムの「メイン」Bean を取得するために main() で getBean() を使用すると述べています。

私が getBean() を使用したもう 1 つの用途は、対話型のユーザー構成によって特定の状況に応じた Bean 構成が決定される場合です。そのため、たとえば、ブート システムの一部は、scope='prototype' Bean 定義を持つ getBean() を使用してデータベース テーブルをループし、追加のプロパティを設定します。おそらく、アプリケーション コンテキスト XML を (再) 書き込むよりも使いやすいデータベース テーブルを調整する UI が存在すると考えられます。

getBeanを使用することが理にかなっている場合があります。既に存在するシステムを再構成する場合、依存関係はスプリングコンテキストファイルで明示的に呼び出されません。 getBeanを呼び出すことでプロセスを開始できるため、一度にすべてを接続する必要はありません。このようにして、ゆっくりとスプリング構成を構築し、時間の経過とともに各ピースを所定の位置に配置し、ビットを適切に整列させることができます。最終的にgetBeanの呼び出しは置き換えられますが、コードの構造またはコードの構造を理解すると、より多くのBeanをワイヤリングし、getBeanの呼び出しをますます少なくするプロセスを開始できます。

ただし、サービスロケーターパターンが必要な場合もあります。 たとえば、コントローラーBeanがある場合、このコントローラーにはいくつかのデフォルトのサービスBeanがあります。これは、構成によって依存性を注入できます。 また、このコントローラーが今または後で呼び出すことができる追加または新しいサービスが多数存在する可能性があります。これらのサービスには、サービスBeanを取得するためのサービスロケーターが必要です。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top