动态注入spring bean

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

在此处输入图像描述

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

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

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

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

  1. 将注入正确的实现。 例如,当设置myinterface.type=implB ImplB将被注入到使用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; } } } 

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

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

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

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

 @Component public class MyInterfaceImplementationsHolder { @Autowired private Map implementations; public MyInterface get(String impl) { return this.implementations.get(impl); } } 

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

现在,无论您需要注入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; } } 

最后,你需要一个bean,它实际上改变了每个具有MyInterface作为属性的bean的实现:

 @Component public class MyInterfaceImplementationUpdater { @Autowired private Map reloaders; @Autowired private MyInterfaceImplementationsHolder holder; public void updateImplementations(String implBeanName) { this.reloaders.forEach((k, v) -> v.changeImplementation(this.holder.get(implBeanName))); } } 

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

每当您希望更改实现时,您应该只使用新实现的bean的名称调用updateImplementations方法,该实现是类的较低驼峰大小写简单名称,即类MyImplAMyImplB myImplAMyImplB

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

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

让VehicleRepairService成为一个接口:

 public interface VehicleRepairService { void repair(); } 

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

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

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

希望能帮助到你。

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

如果是这样,那么您可以尝试结合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. } } 

applicationContext.xml中

      

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上下文不能同时包含两个实例,那么您可以尝试将ServiceLocatorFactoryBean与prototype目标bean范围和目标实现类的Conditional注释一起使用。 可以使用此方法代替MyRegistry

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

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

几乎在运行时需要不同的bean作为依赖关系时,您需要使用原型范围。 然后,您可以使用配置返回原型bean的不同实现。 您将需要处理自己返回实现的逻辑,(它们甚至可以返回2个不同的单例bean并不重要)但是说您想要新的bean,并且返回实现的逻辑位于名为SomeBeanWithLogic.isSomeBooleanExpression()的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的实现”。 这可以通过代理设计模式来完成。

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

您可以在属性值上使用Spring @Conditional。 为两个Bean指定相同的名称,它应该起作用,因为只创建一个实例。

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

请注意 – 如果有兴趣知道 – FileChangedReloadingStrategy会使您的项目高度依赖于部署条件:WAR / EAR应该按容器展开,您应该可以直接访问文件系统,这些条件在所有情况下都不会得到满足和环境。