18

依存性注入にSpringを使用しているシステムがあります。注釈ベースの自動配線を使用します。Bean はコンポーネント スキャンによって検出されます。つまり、コンテキスト XML には次のものが含まれます。

<context:component-scan base-package="org.example"/>

私の問題を説明するために、以下のうなずく例を作成しました。

Zooオブジェクトのコンテナである がありAnimalます。の開発者は、開発中にどのオブジェクトが含まれるZooかを知りません。Spring によってインスタンス化された具体的なオブジェクトのセットはコンパイル時に認識されますが、さまざまなビルド プロファイルが存在するため、さまざまな のセットが生成され、これらの状況下でのコードを変更してはなりません。AnimalZooAnimalAnimalZoo

の目的は、特定の に明示的に依存する必要なく、実行時にZooシステムの他の部分 (ここでは として示されているZooPatron) が一連のオブジェクトにアクセスできるようにすることです。AnimalAnimal

実際、具象Animalクラスはすべて、さまざまな Maven アーティファクトによって提供されます。これらの具象を含むさまざまなアーティファクトに依存するだけでプロジェクトのディストリビューションを組み立て、Animalコンパイル時にすべてを正しく自動配線できるようにしたいと考えています。

Animal私は、個人が に依存するようにして、 duringZooで登録メソッドを呼び出せるようにすることで、この問題を解決しようとしました (失敗しました) 。これにより、 が の明示的なリストに明示的に依存することが回避されます。Zoo@PostConstructZooAnimal

このアプローチの問題点は、すべての が登録されているZoo場合にのみ、顧客が対話を希望することです。コンパイル時に既知の s の有限セットがあり、登録はすべてライフサイクルの Spring ワイヤリング フェーズ中に行われるため、サブスクリプション モデルは不要である必要があります (つまり、実行時にs に s を追加したくありません)。)。AnimalAnimalAnimalZoo

残念ながら、 のすべての顧客はZoo単に に依存していますZoo。これは、Animalが と持っている関係とまったく同じZooです。したがって、 と のメソッド@PostConstructは任意の順序で呼び出されます。これは、以下のコード例で示されています。 on が呼び出された時点で、 が登録されていません。数ミリ秒後にすべて登録されます。AnimalZooPatron@PostConstructZooPatronAnimal

したがって、ここには 2 種類の依存関係があり、Spring で表現するのに苦労しています。の顧客は、すべてのs が入った後にZooのみ使用したいと考えています。Animal(おそらく「アーク」がより良い例だったでしょう...)

私の質問は基本的に、この問題を解決する最善の方法は何ですか?

@Component
public class Zoo {

    private Set<Animal> animals = new HashSet<Animal>();

    public void register(Animal animal) {
        animals.add(animal);
    }

    public Collection<Animal> getAnimals() {
        return animals;
    }

}

public abstract class Animal {

    @Autowired
    private Zoo zoo;

    @SuppressWarnings("unused")
    @PostConstruct
    private void init() {
        zoo.register(this);
    }

    @Component
    public static class Giraffe extends Animal {
    }

    @Component
    public static class Monkey extends Animal {
    }

    @Component
    public static class Lion extends Animal {
    }

    @Component
    public static class Tiger extends Animal {
    }

}

public class ZooPatron {

    public ZooPatron(Zoo zoo) {
        System.out.println("There are " + zoo.getAnimals().size()
                             + " different animals.");
    }

}

@Component
public class Test {

    @Autowired
    private Zoo zoo;

    @SuppressWarnings("unused")
    @PostConstruct
    private void init() {
        new Thread(new Runnable() {
            private static final int ITERATIONS = 10;
            private static final int DELAY = 5;
            @Override
            public void run() {
                for (int i = 0; i<ITERATIONS; i++) {
                    new ZooPatron(zoo);
                    try {
                        Thread.sleep(DELAY);
                    } catch (InterruptedException e) {
                        // nop
                    }
                }
            }
        }).start();     
    }

}

public class Main {

    public static void main(String... args) {
        new ClassPathXmlApplicationContext("/context.xml");
    }

}

出力:

There are 0 different animals.
There are 3 different animals.
There are 4 different animals.
There are 4 different animals.
... etc

承認された解決策の説明

@PostConstruct基本的に答えは次のとおりです。いいえ、 Spring の「外部」に移動するか、その動作を変更しない限り、呼び出しの順序を保証することはできません。

ここでの本当の問題は、呼び出しを順序付けしたかったことではなく、依存関係が正しく表現されてい@PostConstructないという単なる兆候でした。

の消費者がZoo彼に依存し、さらにs にZoo依存している場合Animal、すべてが正しく機能します。Zoo私の間違いは、サブクラスの明示的なリストに依存したくなかったAnimalため、この登録方法を導入したことです。回答で指摘されているように、自己登録メカニズムと依存性注入を組み合わせても、不必要な複雑さがなければ機能しません。

答えは、 がのコレクションZoo依存していることを宣言し、Spring がオートワイヤリングによってコレクションにデータを入力できるようにすることです。Animal

したがって、コレクション メンバーのハード リストはなく、Spring によって検出されますが、依存関係が正しく表現されているため、@PostConstruct必要な順序でメソッドが実行されます。

素晴らしい答えをありがとう。

4

5 に答える 5

2

依存関係を導入せずに @PostConstruct の順序を確保する方法はないと思います。

インジェクションまたは自己登録を混在させようとして問題を探していると思います。@PostConstruct の呼び出し順序は、ある程度は問題になりません。問題がある場合は、ジョブに適したツールではない可能性があります。

あなたの例のためのいくつかのアイデア

  • Zoo#animals に @Autowired を設定してみてください: 動物による自己登録は必要ありません。
  • 登録を維持するが、登録は外部のアクターに任せる (誰かが動物を動物園に入れているよね? - 動物は自分で入り口に現れるわけではない)
  • いつでも新しい動物を挿入する必要があるが、手動で挿入したくない場合は、zoo でより動的なアクセサーを実行します。動物のリストを保存するのではなく、Spring コンテキストを使用して、インターフェイスの既存のインスタンスをすべて取得します。

「正しい」答えはないと思います。それはすべてユースケースに依存します。

于 2011-08-15T16:13:26.387 に答える
1

最善の方法、IMO は、オブジェクト グラフの構築中にあまり多くの作業を行わないようにし (Java の場合と同様に、コンストラクターで多くの作業を行うことを避けます)、不明な場合に依存関係からメソッドを呼び出さないようにすることですそれらはまだ完全に初期化されています。

メソッドから @PostConstruct アノテーションを削除しTest#init()、コンテキストが作成された後にメイン メソッドから呼び出すだけであれば、この問題は発生しません。

于 2011-08-15T15:31:16.203 に答える
0

あなたの場合、Zooオブジェクトとすべての動物タイプの間に依存関係があるように見えます。この依存関係を反映するようにZooオブジェクトを設計すると、問題は解決します。たとえば、次のことができます。

<bean id="zoo" class="Zoo">
<property name="animals">
<list>
<ref bean="Monkey" />
<ref bean="Tiger" />
<ref bean="Lion" />
</list>
</property>
</bean>

登録方法を使用する代わりに。

于 2011-08-15T15:53:26.150 に答える