SpringのApplicationContext.getBeanが悪いと考えられるのはなぜですか?


270

私は一般的なSpringの質問をしました:Spring Bean自動キャストし、Springの呼び出しApplicationContext.getBean()はできるだけ避けるべきであると複数の人に回答してもらいました。何故ですか?

Springが作成するように構成したBeanに他にどのようにアクセスする必要がありますか?

私は非WebアプリケーションでSpringを使用しており、LiorHで説明されているように共有ApplicationContextオブジェクトにアクセスすることを計画していました

修正

私は以下の答えを受け入れますが、ここでは、依存性注入とサービスロケータを使用することのメリットについて議論するMartin Fowlerによる別の見解を示します(これは、本質的にはwrappedを呼び出すのと同じApplicationContext.getBean()です)。

Fowler氏は、次のように述べています。「サービスロケータを使用すると、アプリケーションクラスはロケータへのメッセージによって明示的に[サービス]を要求します。注入を使用すると、明示的な要求はなく、サービスはアプリケーションクラスに表示されます。つまり、制御が反転します。制御の反転はフレームワークの一般的な機能ですが、代償を伴うものです。デバッグしようとすると、理解が難しくなり、問題が発生する傾向があります。そのため、全体としては、[制御の反転]は避けたいと思います。 ]私はそれを必要とする場合は除きます。これは、私はそれがより簡単な代替の上に自分自身を正当化するために必要だと思いばかりということ、それは悪いことだと言っているわけではありません。

回答:


202

これについては別の質問のコメントで述べましたが、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("myApplication")が、getBean()他の場所を呼び出す必要はありません。


3
オブジェクトの作成時に注釈だけで機能するこれに関連するものはありnew MyOtherClass()ますか?私は@Autowiredについて知っているが、私は今までフィールド上でそれを使用しましたし、それが上壊すnew MyOtherClass()...
ティム・

70
ApplicationContext.getBean()がIoCではないのは事実ではありません。Nietherは、すべてのクラスをSpringでインスタンス化することを義務付けています。それは不適切な教義です。ApplicationContext自体が注入される場合は、この方法でBeanをインスタンス化するように要求しても問題ありません。作成するBeanは、最初に注入されたApplicationContextに基づいて異なる実装にすることができます。たとえば、コンパイル時に不明なBean名に基づいて新しいBeanインスタンスを動的に作成するシナリオがありますが、spring.xmlファイルで定義されている実装の1つと一致します。
Alex Worden、2011年

3
Alexに同意します。同じ問題があり、ファクトリークラスは実行時にユーザーインタラクションを介して使用するBeanまたは実装のみを認識します。これがContextAwareインターフェースの出番であると思います
Ben

3
@elbek:applicationContext.getBean依存関係の注入ではありません。サービスロケータとして使用して、フレームワークに直接アクセスしています
ColinD 2013年

6
@herman:長い間使用していなかったので、Springについては知りませんが、JSR-330 / Guice / Daggerでは、aのProvider<Foo>代わりにをインジェクトし、必要になるたびにFooを呼び出すことでこれを行いprovider.get()ます。新しいインスタンス。コンテナ自体への参照はなく、Providerテスト用のを簡単に作成できます。
ColinD 2013年

64

制御の反転(IoC)よりもサービスロケーターを選択する理由は次のとおりです。

  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コンテキストはコンパイル時に効果的にバインドされます(展開されたwarでオペレーターがコンテキストをグラブする必要がない限り)。Springでプロパティファイルを使用することもできますが、サーブレットを使用する場合、プロパティファイルは所定の場所に配置する必要があります。つまり、同じボックスに同時に複数のサーブレットをデプロイすることはできません。SpringとJNDIを使用してサーブレットの起動時にプロパティを変更できますが、管理者が変更可能なパラメーターにJNDIを使用している場合は、Spring自体の必要性は少なくなります(JNDIは事実上サービスロケーターであるため)。

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

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


7
さらに、ServiceLocatorは常にSpringのIoCを使用して、Springに依存しないようにコードを抽象化したり、Springアノテーションを散らかしたり、判読不能なマジックを使用したりできます。私は最近、SpringがサポートされていないGoogleAppEngineに一連のコードを移植しました。そもそも、ServiceFactoryの背後にすべてのIoCを隠したいと思います。
Alex Worden

IoCは貧血ドメインモデルを奨励しますが、私はそれを軽視します。エンティティBeanには、サービスを検索して独自の動作を実装できる方法が必要です。その層では、サービスロケーターを必要とすることを本当に回避することはできません。
Joel

4
ビザー。私は常にアノテーションを使用してSpringを使用しています。確かにある程度の学習曲線は関係しますが、今では、メンテナンス、デバッグ、明快さ、読みやすさの点で何の問題もありません...構造化の仕方がトリックだと思います。
ローレンス

25

クラスを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;    
    }
}

アプリの一部の側面を使用する必要があるスタンドアロンクラス(テストなど)がある場合、これを数回行う必要がありましたが、それがアプリケーションコンテキストに含めたくないので、実際にはアプリの一部です。これにより、いつも醜いと思っていたString名を使用してBeanを検索する必要がなくなることにも注意してください。


このメソッド@Autowiredもアノテーションでうまく使用できました。
blong 2013年

21

Springのようなものを使用する最もクールな利点の1つは、オブジェクトを相互にワイヤリングする必要がないことです。Zeusの頭が開き、クラスが表示されます。必要に応じて、作成されたすべての依存関係が組み込まれ、完全に形成されます。それは魔法で幻想的です。

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

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


1
しかし、OPは「ClassINeed」と言っておらず、「BeanNameINeed」と言っています。これにより、IoCコンテナは、任意の方法で構成されたクラスにインスタンスを作成できます。おそらくIoCよりも「サービスロケータ」パターンに似ていますが、それでも疎結合になります。
HDave 2013

16

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

コンテナーは、コードからは見えないものであり、尋ねられることなく魔法のようにそのニーズに対応していると考えてください。

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


11

@Autowiredor を使用することApplicationContext.getBean()は本当に同じことです。どちらの方法でも、コンテキストで構成されたBeanを取得できます。また、どちらの方法でも、コードはSpringに依存しています。避けるべき唯一のことは、ApplicationContextをインスタンス化することです。これは1回だけ実行してください。つまり、次のような行

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

アプリケーションで1回だけ使用してください。


いいえ。@AutowiredまたはApplicationContext.getBean()によって、まったく異なるBeanが生成される場合があります。どのように起こったかはわかりませんが、現在この問題に取り組んでいます。
Oleksandr_DJ

4

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

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

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


4

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

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


4

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


4

理由の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への依存を削除でき、テスト開発者は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直接を使用することは自動的に間違っているとは言えず、すべてのコストで回避する必要があります。ただし、より良いオプションがある場合(そしてほとんどの場合にあります)、より良いオプションを使用してください。


3

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

また、main()でgetBean()を使用して、スタンドアロンプ​​ログラムの「メイン」Beanをフェッチすることについて言及している人もいます。

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


3

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


2

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


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.