34

Java-spring web-app では、Bean を動的に注入できるようにしたいと考えています。たとえば、2 つの異なる実装を持つインターフェイスがあります。

ここに画像の説明を入力

私のアプリでは、いくつかのプロパティ ファイルを使用してインジェクションを構成しています。

#Determines the interface type the app uses. Possible values: implA, implB
myinterface.type=implA

私のインジェクションは、プロパティ ファイル内のプロパティ値を条件付きで中継して実際にロードされました。たとえば、この場合 myinterface.type=implA MyInterface を注入する場所はどこでも、注入される実装は ImplA になります ( Conditional アノテーションを拡張することで実現しました)。

実行時にそれを希望します-プロパティが変更されると、次のことが起こります(サーバーの再起動なし):

  1. 適切な実装が注入されます。たとえば、myinterface.type=implBImplB を設定すると、MyInterface が使用されている場所にいつでも注入されます
  2. Spring Environmentを新しい値でリフレッシュし、Bean にも再注入する必要があります。

コンテキストを更新することを考えましたが、それは問題を引き起こします。インジェクションにセッターを使用し、プロパティが再構成されたらそれらのセッターを再利用することを考えました。そのような要件のための作業慣行はありますか?

何か案は?

アップデート

一部の人が示唆したように、両方の実装 (ImplA と ImplB) を保持するファクトリ/レジストリを使用し、関連するプロパティをクエリして正しいものを返すことができます。もしそうしても、私にはまだ 2 番目の課題があります。それは環境です。たとえば、レジ​​ストリが次のようになっているとします。

@Service
public class MyRegistry {

private String configurationValue;
private final MyInterface implA;
private final MyInterface implB;

@Inject
public MyRegistry(Environmant env, MyInterface implA, MyInterface ImplB) {
        this.implA = implA;
        this.implB = implB;
        this.configurationValue = env.getProperty("myinterface.type");
}

public MyInterface getMyInterface() {
        switch(configurationValue) {
        case "implA":
                return implA;
        case "implB":
                return implB;
        }
}
}

プロパティが変更されたら、環境に再注入する必要があります。そのための提案はありますか?

コンストラクターの代わりにメソッド内でその env を照会できることはわかっていますが、これはパフォーマンスの低下であり、環境を再注入するための ider を考えたいと思います (これもセッター注入を使用する可能性がありますか?)。

4

7 に答える 7

30

このタスクはできるだけシンプルに保ちます。起動時にインターフェースの 1 つの実装を条件付きでロードしMyInterfaceてから、同じインターフェースの別の実装の動的ロードをトリガーするイベントを起動する代わりに、実装と保守がはるかに簡単な別の方法でこの問題に取り組みます。

まず、考えられるすべての実装をロードします。

@Component
public class MyInterfaceImplementationsHolder {

    @Autowired
    private Map<String, MyInterface> implementations;

    public MyInterface get(String impl) {
        return this.implementations.get(impl);
    }
}

MyInterfaceこの Bean は、インターフェースのすべての実装の単なるホルダーです。ここには魔法はなく、Spring の自動配線の一般的な動作です。

の特定の実装を注入する必要がある場合MyInterfaceは、インターフェースを使用してそれを行うことができます。

public interface MyInterfaceReloader {

    void changeImplementation(MyInterface impl);
}

次に、実装の変更を通知する必要があるすべてのクラスに対して、MyInterfaceReloaderインターフェイスを実装するだけです。例えば:

@Component
public class SomeBean implements MyInterfaceReloader {

    // Do not autowire
    private MyInterface myInterface;

    @Override
    public void changeImplementation(MyInterface impl) {
        this.myInterface = impl;
    }
}

MyInterface最後に、属性として持つすべての Bean の実装を実際に変更する Bean が必要です。

@Component
public class MyInterfaceImplementationUpdater {

    @Autowired
    private Map<String, MyInterfaceReloader> reloaders;

    @Autowired
    private MyInterfaceImplementationsHolder holder;

    public void updateImplementations(String implBeanName) {
        this.reloaders.forEach((k, v) -> 
            v.changeImplementation(this.holder.get(implBeanName)));
    }
}

これは、インターフェイスを実装するすべての Bean を単純に自動配線MyInterfaceReloaderし、ホルダーから取得されて引数として渡される新しい実装でそれぞれを更新します。繰り返しますが、一般的な Spring 自動配線ルールです。

実装を変更したい場合はいつでもupdateImplementations、新しい実装の Bean の名前でメソッドを呼び出す必要があります。これは、キャメル ケースの小文字の単純なクラス名です。つまりmyImplAmyImplBクラスMyImplAMyImplB.

インターフェイスを実装するすべての Bean に初期実装が設定されるように、起動時にこのメソッドを呼び出す必要もありMyInterfaceReloaderます。

于 2016-11-02T02:48:48.007 に答える
16

org.apache.commons.configuration.PropertiesConfiguration と org.springframework.beans.factory.config.ServiceLocatorFactoryBean を使用して、同様の問題を解決しました。

VehicleRepairService をインターフェースとします。

public interface VehicleRepairService {
    void repair();
}

CarRepairService と TruckRepairService は、それを実装する 2 つのクラスです。

public class CarRepairService implements VehicleRepairService {
    @Override
    public void repair() {
        System.out.println("repair a car");
    }
}

public class TruckRepairService implements VehicleRepairService {
    @Override
    public void repair() {
        System.out.println("repair a truck");
    }
}

サービス ファクトリのインターフェイスを作成します。

public interface VehicleRepairServiceFactory {
    VehicleRepairService getRepairService(String serviceType);
}

Config を構成クラスとして使用します。

@Configuration()
@ComponentScan(basePackages = "config.test")
public class Config {
    @Bean 
    public PropertiesConfiguration configuration(){
        try {
            PropertiesConfiguration configuration = new PropertiesConfiguration("example.properties");
            configuration
                    .setReloadingStrategy(new FileChangedReloadingStrategy());
            return configuration;
        } catch (ConfigurationException e) {
            throw new IllegalStateException(e);
        }
    }

    @Bean
    public ServiceLocatorFactoryBean serviceLocatorFactoryBean() {
        ServiceLocatorFactoryBean serviceLocatorFactoryBean = new ServiceLocatorFactoryBean();
        serviceLocatorFactoryBean
                .setServiceLocatorInterface(VehicleRepairServiceFactory.class);
        return serviceLocatorFactoryBean;
    }

    @Bean
    public CarRepairService carRepairService() {
        return new CarRepairService();
    }

    @Bean
    public TruckRepairService truckRepairService() {
        return new TruckRepairService();
    }

    @Bean
    public SomeService someService(){
        return new SomeService();
    }
}

FileChangedReloadingStrategyを使用すると、プロパティ ファイルを変更したときに構成がリロードされます。

service=truckRepairService
#service=carRepairService

サービスに構成とファクトリがあると、プロパティの現在の値を使用してファクトリから適切なサービスを取得できます。

@Service
public class SomeService  {

    @Autowired
    private VehicleRepairServiceFactory factory;

    @Autowired 
    private PropertiesConfiguration configuration;


    public void doSomething() {
        String service = configuration.getString("service");

        VehicleRepairService vehicleRepairService = factory.getRepairService(service);
        vehicleRepairService.repair();
    }
}

それが役に立てば幸い。

于 2016-11-02T10:30:38.723 に答える
5

これは重複した質問であるか、少なくとも非常に似ている可能性があります。とにかく、この種の質問にここで答えました: Spring bean partial autowire プロトタイプコンストラクター

実行時に依存関係に別の Bean が必要な場合は、プロトタイプ スコープを使用する必要があります。次に、構成を使用して、プロトタイプ Bean のさまざまな実装を返すことができます。実装が自分自身を返すロジックを処理する必要があります (それらは 2 つの異なるシングルトン Bean を返すことさえありますが、問題ではありません) SomeBeanWithLogic.isSomeBooleanExpression()。次に、構成を行うことができます。

@Configuration
public class SpringConfiguration
{

    @Bean
    @Autowired
    @Scope("prototype")
    public MyInterface createBean(SomeBeanWithLogic someBeanWithLogic )
    {
        if (someBeanWithLogic .isSomeBooleanExpression())
        {
            return new ImplA(); // I could be a singleton bean
        }
        else
        {
            return new ImplB();  // I could also be a singleton bean
        }
    }
}

コンテキストをリロードする必要はありません。たとえば、Bean の実装を実行時に変更する場合は、上記を使用します。この Bean がシングルトン Bean または何か奇妙なもののコンストラクターで使用されたために、本当にアプリケーションをリロードする必要がある場合は、設計を再考する必要があり、これらの Bean が本当にシングルトン Bean であるかどうかを確認する必要があります。異なる実行時の動作を実現するためにシングルトン Bean を再作成するためにコンテキストをリロードするべきではありません。これは必要ありません。

編集この回答の最初の部分は、Bean の動的注入に関する質問に回答しています。尋ねられたとおりですが、質問は「実行時にシングルトン Bean の実装を変更するにはどうすればよいか」という 1 つに近いと思います。これは、プロキシ デザイン パターンを使用して実行できます。

interface MyInterface 
{
    public String doStuff();
}

@Component
public class Bean implements MyInterface
{
    boolean todo = false; // change me as needed

    // autowire implementations or create instances within this class as needed
    @Qualifier("implA")
    @Autowired
    MyInterface implA;

    @Qualifier("implB")
    @Autowired
    MyInterface implB;

    public String doStuff()
    {
        if (todo)
        {
            return implA.doStuff();
        }
        else
        {
            return implB.doStuff();
        }
    }   
}
于 2016-11-01T23:07:56.883 に答える
1

知っておくと興味深い場合は、FileChangedReloadingStrategy を使用すると、プロジェクトが展開条件に大きく依存するようになることに注意してください。WAR/EAR はコンテナーによって展開され、ファイル システムに直接アクセスできる必要があります。これらの条件は、すべての状況で常に満たされるとは限りません。と環境。

于 2016-11-05T08:05:42.827 に答える
0

プロパティ値で Spring @Conditional を使用できます。両方の Bean に同じ名前を付けると、1 つのインスタンスのみが作成されるため、機能するはずです。

サービスとコンポーネントで @Conditional を使用する方法については、こちらをご覧ください: http://blog.codeleak.pl/2015/11/how-to-register-components-using.html

于 2016-10-18T11:39:06.753 に答える