Java 动态注入spring bean

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/40105044/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 22:24:08  来源:igfitidea点击:

Inject spring bean dynamically

javaspringpolymorphismspring-environment

提问by forhas

In a java-spring web-app I would like to be able to dynamically inject beans. For example I have an interface with 2 different implementations:

在 java-spring web-app 中,我希望能够动态注入 bean。例如,我有一个具有 2 个不同实现的接口:

enter image description here

在此处输入图片说明

In my app I'm using some properties file to configure injections:

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

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

My injections actually loaded conditionally relaying on the properties values in the properties file. For example in this case myinterface.type=implA wherever I inject MyInterface the implementation that will be injected will be ImplA (I accomplished that by extending the Conditional annotation).

我的注入实际上是根据属性文件中的属性值有条件地加载。例如,在这种情况下 myinterface.type=implA 无论我在哪里注入 MyInterface,都将注入的实现将是 ImplA(我通过扩展Conditional annotation实现了这一点)。

I would like that during runtime - once the properties are changed the following will happen (without server restart):

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

  1. The right implementation will be injected. For example when setting myinterface.type=implBImplB will be injected where-ever MyInterface is used
  2. Spring Environmentshould be refreshed with the new values and re-injected as well to beans.
  1. 将注入正确的实现。例如,当设置myinterface.type=implBImplB 将被注入到任何使用 MyInterface 的地方时
  2. Spring Environment应该用新值刷新并重新注入 bean。

I thought of refreshing my context but that creates problems. I thought maybe to use setters for injection and re-use those setters once properties are re-configured. Is there a working practice for such a requirement?

我想刷新我的上下文,但这会产生问题。我想也许可以使用 setter 进行注入,并在重新配置属性后重新使用这些 setter。是否有针对此类要求的工作实践?

Any ideas?

有任何想法吗?

UPDATE

更新

As some suggested I can use a factory/registry that holds both implementations (ImplA and ImplB) and returns the right one by querying the relevant property. If I do that I still have the second challenge - the environment. for example if my registry looks like this:

正如一些人所建议的,我可以使用一个工厂/注册表来保存两个实现(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;
        }
}
}

Once property has changed I should re-inject my environment. any suggestions for that?

一旦属性发生变化,我应该重新注入我的环境。对此有何建议?

I know I can query that env inside the method instead of constructor but this is a performance reduction and also I would like to think of an ider for re-injecting environment (again, maybe using a setter injection?).

我知道我可以在方法中查询 env 而不是构造函数,但这会降低性能,而且我想考虑一个用于重新注入环境的 ider(同样,也许使用 setter 注入?)。

回答by Dennis Ich

You can use Spring @Conditional on a property value. Give both Beans the same name and it should work as only one Instance will be created.

您可以在属性值上使用 Spring @Conditional。为两个 Bean 提供相同的名称,它应该可以正常工作,因为只会创建一个实例。

Have a look here on how to use @Conditional on Services and Components: http://blog.codeleak.pl/2015/11/how-to-register-components-using.html

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

回答by Sergey Bespalov

If I understand you correctly then the goal is not to replace injected object instances but to use different implementations during interface method call depends on some condition at run time.

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

If it is so then you can try to look at the SringTargetSourcemechanism in combination with ProxyFactoryBean. The point is that proxy objects will be injected to beans that uses your interface, and all the interface method calls will be sent to TargetSourcetarget.

如果是这样,那么您可以尝试结合ProxyFactoryBean来查看SringTargetSource机制。关键是代理对象将被注入到使用您的接口的 bean 中,并且所有接口方法调用都将发送到目标。TargetSource

Let's call this "Polymorphic Proxy".

我们称之为“多态代理”。

Have a look at example below:

看看下面的例子:

ConditionalTargetSource.java

条件目标源.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

应用上下文.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

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

}

Also if you want to have both MyInterfaceimplementations to be Spring beans, and the Spring context could not contains both instances at the same time then you can try to use ServiceLocatorFactoryBeanwith prototypetarget beans scope and Conditionalannotation on target implementation classes. This approach can be used instead of MyRegistry.

此外,如果您想让两个MyInterface实现都成为 Spring bean,并且 Spring 上下文不能同时包含两个实例,那么您可以尝试使用ServiceLocatorFactoryBeanprototype目标 bean 范围和Conditional目标实现类上的注释。可以使用此方法代替MyRegistry.

P.S.Probably Application Context refresh operation also can do what you want but it can cause other problems such as performance overheads.

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

回答by Snickers3192

This may be a duplicate question or at least very similar, anyway I answered this sort of question here: Spring bean partial autowire prototype constructor

这可能是一个重复的问题或至少非常相似,无论如何我在这里回答了这类问题:Spring bean 部分自动装配原型构造函数

Pretty much when you want a different beans for a dependency at run-time you need to use a prototype scope. Then you can use a configuration to return different implementations of the prototype bean. You will need to handle the logic on which implementation to return yourself, (they could even be returning 2 different singleton beans it doesn't matter) But say you want new beans, and the logic for returning the implementation is in a bean called SomeBeanWithLogic.isSomeBooleanExpression(), then you can make a configuration:

几乎当您需要在运行时为依赖项使用不同的 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
        }
    }
}

There should never be a need to reload the context. If for instance, you want the implementation of a bean to change at run-time, use the above. If you really need to reload your application, because this bean was used in constructors of a singleton bean or something weird, then you need to re-think your design, and if these beans are really singleton beans. You shouldn't be reloading the context to re-create singleton beans to achieve different run-time behavior, that is not needed.

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

EditThe first part of this answer answered the question about dynamically injecting beans. As asked, but I think the question is more of one: 'how can I change the implementation of a singleton bean at run-time'. This could be done with a proxy design pattern.

编辑此答案的第一部分回答了有关动态注入 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();
        }
    }   
}

回答by Federico Peralta Schaffner

I would keep this task as simple as possible. Instead of conditionally load one implementation of the MyInterfaceinterface at startup and then fire an event that triggers dynamic loading of another implementation of the same interface, I would tackle this problem in a different way, that is much simpler to implement and maintain.

我会让这个任务尽可能简单。与其MyInterface在启动时有条件地加载接口的一个实现,然后触发触发动态加载同一接口的另一个实现的事件,我会以不同的方式解决这个问题,这更容易实现和维护。

First of all, I'd just load all possible implementations:

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

@Component
public class MyInterfaceImplementationsHolder {

    @Autowired
    private Map<String, MyInterface> implementations;

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

This bean is just a holder for all implementations of the MyInterfaceinterface. Nothing magic here, just common Spring autowiring behavior.

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

Now, wherever you need to inject a specific implementation of MyInterface, you could do it with the help of an interface:

现在,无论您在何处需要注入 的特定实现MyInterface,都可以借助接口来完成:

public interface MyInterfaceReloader {

    void changeImplementation(MyInterface impl);
}

Then, for every class that needs to be notifiedof a change of the implementation, just make it implement the MyInterfaceReloaderinterface. For instance:

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

@Component
public class SomeBean implements MyInterfaceReloader {

    // Do not autowire
    private MyInterface myInterface;

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

Finally, you need a bean that actually changes the implementation in every bean that has MyInterfaceas an attribute:

最后,您需要一个 bean 来实际更改每个具有MyInterface属性的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)));
    }
}

This simply autowires all beans that implement the MyInterfaceReloaderinterface and updates each one of them with the new implementation, which is retrieved from the holder and passed as an argument. Again, common Spring autowiring rules.

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

Whenever you want the implementation to be changed, you should just invoke the updateImplementationsmethod with the name of the bean of the new implementation, which is the lower camel case simple name of the class, i.e. myImplAor myImplBfor classes MyImplAand MyImplB.

每当您想要更改实现时,您应该只updateImplementations使用新实现的 bean 的名称调用该方法,该名称是类的小驼峰简单名称,即myImplAmyImplB对于类MyImplAMyImplB

You should also invoke this method at startup, so that an initial implementation is set on every bean that implements the MyInterfaceReloaderinterface.

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

回答by eltabo

I solved a similar issue by using org.apache.commons.configuration.PropertiesConfiguration and org.springframework.beans.factory.config.ServiceLocatorFactoryBean:

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

Let VehicleRepairService be an interface:

让 VehicleRepairService 成为一个接口:

public interface VehicleRepairService {
    void repair();
}

and CarRepairService and TruckRepairService two classes that implements it:

和 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");
    }
}

I create an interface for a service factory:

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

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

Let use Config as configuration class:

让我们使用 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();
    }
}

By using FileChangedReloadingStrategyyour configuration be reload when you change the property file.

通过使用FileChangedReloadingStrategy,您的配置将在您更改属性文件时重新加载。

service=truckRepairService
#service=carRepairService

Having the configuration and the factory in your service, let you can get the appropriate service from the factory using the current value of the property.

在您的服务中拥有配置和工厂,让您可以使用属性的当前值从工厂获取适当的服务。

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

Hope it helps.

希望能帮助到你。

回答by p3consulting

Be aware that - if interesting to know about - FileChangedReloadingStrategy makes your project highly dependent on the deployment conditions: the WAR/EAR should be exploded by container and your should have direct access to the file system, conditions that are not always met in all situations and environments.

请注意 - 如果有趣的话 - FileChangedReloadingStrategy 使您的项目高度依赖于部署条件:WAR/EAR 应该由容器分解,并且您应该可以直接访问文件系统,这些条件并非总是在所有情况下都满足和环境。

回答by Vaibhaw K

You can use @Resource annotation for injection as originally answered here

您可以使用 @Resource 注释进行注入,如最初在此处回答的那样

e.g.

例如

@Component("implA")
public class ImplA implements MyInterface {
  ...
}
@Component("implB")
public class ImplB implements MyInterface {
  ...
}
@Component
public class DependentClass {

  @Resource(name = "${myinterface.type}") 
  private MyInterface impl;

}

and then set the implementation type in properties file as -

然后将属性文件中的实现类型设置为 -

myinterface.type=implA