动态注入spring bean

for*_*has 25 java polymorphism spring spring-environment

在java-spring网络应用程序中,我希望能够动态注入bean.例如,我有一个具有2种不同实现的接口:

在此输入图像描述

在我的应用程序中,我正在使用一些属性文件来配置注入:

#Determines the interface type the app uses. Possible values: implA, implB
myinterface.type=implA
Run Code Online (Sandbox Code Playgroud)

我的注入实际上有条件地在属性文件中的属性值上加载.例如,在这种情况下myinterface.type = implA,只要我注入MyInterface,将注入的实现将是ImplA(我通过扩展条件注释完成了).

我希望在运行时 - 一旦属性发生更改,将发生以下情况(无需重新启动服务器):

  1. 将注入正确的实施方案.例如,设置myinterface.type=implBImplB将被注入到使用MyInterface的地方
  2. 应该使用新值刷新Spring Environment并重新注入bean.

我想要刷新我的上下文,但这会产生问题.我想可能会使用setter进行注入,并在重新配置属性后重新使用这些setter.是否有这种要求的工作实践?

有任何想法吗?

UPDATE

正如一些人所建议我可以使用一个工厂/注册表来保存两个实现(ImplA和ImplB)并通过查询相关属性返回正确的实现.如果我这样做,我还有第二个挑战 - 环境.例如,如果我的注册表看起来像这样:

@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;
        }
}
}
Run Code Online (Sandbox Code Playgroud)

一旦属性发生变化,我应该重新注入我的环境.有什么建议吗?

我知道我可以在方法中查询env而不是构造函数,但这是性能降低,我想想一个重​​新注入环境的ider(再次,可能使用setter注入?).

Fed*_*ner 16

我会尽可能简化这项任务.而不是MyInterface在启动时有条件地加载接口的一个实现,然后触发触发动态加载同一接口的另一个实现的事件,我将以不同的方式解决这个问题,这实现和维护要简单得多.

首先,我只是加载所有可能的实现:

@Component
public class MyInterfaceImplementationsHolder {

    @Autowired
    private Map<String, MyInterface> implementations;

    public MyInterface get(String impl) {
        return this.implementations.get(impl);
    }
}
Run Code Online (Sandbox Code Playgroud)

这个bean只是该MyInterface接口所有实现的持有者.这里没有什么神奇之处,只是常见的Spring自动装配行为.

现在,无论您需要注入特定的实现MyInterface,您都可以在界面的帮助下完成:

public interface MyInterfaceReloader {

    void changeImplementation(MyInterface impl);
}
Run Code Online (Sandbox Code Playgroud)

然后,对于需要通知实现更改的每个类,只需使其实现MyInterfaceReloader接口即可.例如:

@Component
public class SomeBean implements MyInterfaceReloader {

    // Do not autowire
    private MyInterface myInterface;

    @Override
    public void changeImplementation(MyInterface impl) {
        this.myInterface = impl;
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,您需要一个实际上在每个具有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)));
    }
}
Run Code Online (Sandbox Code Playgroud)

这只是自动装配实现MyInterfaceReloader接口的所有bean,并使用新的实现更新它们中的每一个,该实现从持有者检索并作为参数传递.同样,常见的Spring自动装配规则.

每当你想要改变实现时,你应该只updateImplementations使用新实现的bean的名称调用该方法,这是该类的较低的驼峰式简单名称,即myImplAmyImplBMyImplAMyImplB.

您还应该在启动时调用此方法,以便在实现该MyInterfaceReloader接口的每个bean上设置初始实现.


elt*_*abo 13

我使用org.apache.commons.configuration.PropertiesConfiguration和org.springframework.beans.factory.config.ServiceLocatorFactoryBean解决了类似的问题:

让VehicleRepairService成为一个接口:

public interface VehicleRepairService {
    void repair();
}
Run Code Online (Sandbox Code Playgroud)

和CarRepairService和TruckRepairService实现它的两个类:

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

我为服务工厂创建了一个接口:

public interface VehicleRepairServiceFactory {
    VehicleRepairService getRepairService(String serviceType);
}
Run Code Online (Sandbox Code Playgroud)

让我们使用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

通过使用FileChangedReloadingStrategy,您可以在更改属性文件时重新加载配置.

service=truckRepairService
#service=carRepairService
Run Code Online (Sandbox Code Playgroud)

通过服务中的配置和工厂,您可以使用属性的当前值从工厂获得适当的服务.

@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();
    }
}
Run Code Online (Sandbox Code Playgroud)

希望能帮助到你.


Ser*_*lov 5

如果我理解正确,那么目标不是替换注入的对象实例,而是在接口方法调用期间使用不同的实现取决于运行时的某些条件.

如果是这样,那么您可以尝试结合ProxyFactoryBean来查看Sring TargetSource机制.关键是代理对象将被注入到使用您的接口的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.
    }

}
Run Code Online (Sandbox Code Playgroud)

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"/>
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

此外,如果您希望两个MyInterface实现都是Spring bean,并且Spring上下文不能同时包含两个实例,那么您可以尝试将ServiceLocatorFactoryBeanprototype目标bean作用域和Conditional目标实现类上的注释一起使用.可以使用这种方法代替MyRegistry.

PS 可能应用程序上下文刷新操作也可以做你想要的,但它可能会导致其他问题,如性能开销.


Sni*_*192 5

这可能是一个重复的问题或至少非常相似,无论如何我在这里回答了此类问题:Spring beanpartial autowire原型构造函数

几乎当您想要在运行时为依赖项使用不同的 bean 时,您需要使用原型范围。然后您可以使用配置来返回原型 bean 的不同实现。您将需要自己处理返回哪个实现的逻辑(它们甚至可能返回 2 个不同的单例 bean,这并不重要)但是假设您想要新的 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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

永远不需要重新加载上下文。例如,如果您希望在运行时更改 bean 的实现,请使用上面的内容。如果您确实需要重新加载您的应用程序,因为这个 bean 被用在单例 bean 的构造函数中或其他奇怪的东西,那么您需要重新考虑您的设计,以及这些 bean 是否真的是单例 bean。您不应该重新加载上下文来重新创建单例 bean 来实现不同的运行时行为,这是不需要的。

编辑这个答案的第一部分回答了有关动态注入bean的问题。正如所问,但我认为问题更多的是:“如何在运行时更改单例 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();
        }
    }   
}
Run Code Online (Sandbox Code Playgroud)

  • 不,这不对。它是关于满足一个接口的多个类。一个特定对象(实例)只有在其状态发生变化时才会改变其行为。作为示例 Bean.doStuff(),仅当 todo 已更改,或者 implA 或 implB 中的数据已更改时才会有所不同。MyInterface 可以有不同的实现,这是因为多态性。但是 doStuff 发生变化是因为状态发生了变化。你很困惑。 (2认同)

Den*_*Ich 0

您可以在属性值上使用 Spring @Conditional。给两个 Bean 指定相同的名称,这样就可以只创建一个实例。

看看这里如何在服务和组件上使用@Conditional: http://blog.codeleak.pl/2015/11/how-to-register-components-using.html