web-dev-qa-db-ja.com

春豆を動的に注入する

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

enter image description here

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

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

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

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

  1. 適切な実装が注入されます。たとえば、myinterface.type=implB ImplBは、MyInterfaceが使用される場所に挿入されます。
  2. Spring Environment を新しい値で更新し、同様にBeanに再注入する必要があります。

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

何か案は?

[〜#〜] update [〜#〜]

一部の人が示唆したように、両方の実装(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を考えたいと思います(再び、おそらくセッター注入を使用しますか?)。

23
forhas

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

まず、すべての可能な実装をロードします。

@Component
public class MyInterfaceImplementationsHolder {

    @Autowired
    private Map<String, MyInterface> implementations;

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

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

これで、MyInterfaceの特定の実装を挿入する必要がある場合はいつでも、インターフェイスの助けを借りてそれを行うことができます。

public interface MyInterfaceReloader {

    void changeImplementation(MyInterface impl);
}

次に、実装の変更のnotifiedである必要があるすべてのクラスについて、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)));
    }
}

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

実装を変更したいときはいつでも、新しい実装のbeanの名前でupdateImplementationsメソッドを呼び出す必要があります。これは、クラスの小文字の単純な名前、つまりmyImplAまたはmyImplBクラスの場合MyImplAおよびMyImplB

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

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();
    }
}

それが役に立てば幸い。

10
eltabo

私が正しく理解している場合、目標は注入されたオブジェクトインスタンスを置き換えることではなく、インターフェイスメソッドの呼び出し中に異なる実装を使用することです。実行時の条件によって異なります。

その場合は、SringTargetSource メカニズムを ProxyFactoryBean と組み合わせて見ることができます。ポイントは、プロキシオブジェクトがインターフェイスを使用するBeanに注入され、すべてのインターフェイスメソッド呼び出しがTargetSourceターゲットに送信されることです。

これを「ポリモーフィックプロキシ」と呼びましょう。

以下の例をご覧ください。

ConditionalTargetSource.Java

@Component
public class ConditionalTargetSource implements TargetSource {

    @Autowired
    private MyRegistry registry;

    @Override
    public Class<?> getTargetClass() {
        return MyInterface.class;
    }

    @Override
    public boolean isStatic() {
        return false;
    }

    @Override
    public Object getTarget() throws Exception {
        return registry.getMyInterface();
    }

    @Override
    public void releaseTarget(Object target) throws Exception {
        //Do some staff here if you want to release something related to interface instances that was created with MyRegistry.
    }

}

applicationContext.xml

<bean id="myInterfaceFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces" value="MyInterface"/>
    <property name="targetSource" ref="conditionalTargetSource"/>
</bean>
<bean name="conditionalTargetSource" class="ConditionalTargetSource"/>

SomeService.Java

@Service
public class SomeService {

  @Autowired
  private MyInterface myInterfaceBean;

  public void foo(){
      //Here we have `myInterfaceBean` proxy that will do `conditionalTargetSource.getTarget().bar()`
      myInterfaceBean.bar();
  }

}

また、両方のMyInterface実装をSpring Beanにしたい場合で、Springコンテキストに両方のインスタンスを同時に含めることができなかった場合は、 ServiceLocatorFactoryBeanprototypeターゲットBeanスコープとターゲット実装クラスのConditionalアノテーション。このアプローチは、MyRegistryの代わりに使用できます。

PS。おそらく、アプリケーションコンテキストの更新操作でも目的の処理を実行できますが、パフォーマンスのオーバーヘッドなどの他の問題が発生する可能性があります。

5
Sergey Bespalov

これは重複した質問または少なくとも非常に似ているかもしれませんが、とにかくこの種の質問に答えました: Spring bean partial autowire prototype constructor

実行時に依存関係に異なるBeanが必要な場合は、ほとんどプロトタイプスコープを使用する必要があります。次に、構成を使用して、プロトタイプBeanのさまざまな実装を返すことができます。自分で返す実装のロジックを処理する必要があります(2つの異なるシングルトンBeanを返すこともできます)。ただし、新しい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を再作成し、さまざまな実行時の動作を実現する必要はありません。これは必要ありません。

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

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();
        }
    }   
}
4
Snickers3192

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

1
p3consulting

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

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

0
Dennis Ich