动态注入Spring Bean
动态注入Spring Bean
在Java-Spring web-app中,我希望能动态注入Bean。例如,我有一个包含2种不同实现的接口:
在我的app中,我使用一些属性文件来配置注入:
#Determines the interface type the app uses. Possible values: implA, implB myinterface.type=implA
我的注入实际上是有条件加载的,依赖于属性文件中的属性值。例如,在这种情况下,myinterface.type=implA,无论我在哪里注入MyInterface,都将注入ImplA(我通过扩展“Conditional注释”实现了这一点)。
我希望在运行时 - 一旦属性被更改,则会发生以下情况(无需重新启动服务器):
- 正确的实现将被注入。例如,在设置
myinterface.type = implB
时,在使用MyInterface的任何地方都将注入ImplB。 - Spring环境应该使用新值进行刷新并重新注入到bean中。
我想过刷新我的上下文,但那会引起问题。我想也许可以使用注入器并在属性重新配置时重复使用它们。有关此要求的有效做法吗?
有任何想法吗?
更新
正如一些人建议的那样,我可以使用一个工厂/注册表来保存两种实现(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; } } }
一旦属性改变,我应该重新注入我的环境。有任何建议吗?
我知道我可以在方法中查询该环境而不是构造函数,但这会减少性能,而且我想考虑一种重新注入环境的解决方法(再次使用setter注入?)。
我使用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(); } }
希望这会有所帮助。
我会尽量简化这个任务。与其在启动时有条件地加载一个MyInterface
接口的实现,然后触发事件来动态加载同一个接口的另一个实现,我会用一种不同的方式来解决这个问题,这种方式更容易实现和维护。
首先,我会加载所有可能的实现:
@Component public class MyInterfaceImplementationsHolder { @Autowired private Mapimplementations; public MyInterface get(String impl) { return this.implementations.get(impl); } }
这个bean只是一个保存了所有MyInterface
接口实现的容器。没有特殊的魔法,只是常见的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 Mapreloaders; @Autowired private MyInterfaceImplementationsHolder holder; public void updateImplementations(String implBeanName) { this.reloaders.forEach((k, v) -> v.changeImplementation(this.holder.get(implBeanName))); } }
这只是自动装配实现了MyInterfaceReloader
接口的所有bean,并使用从容器中检索的新实现作为参数更新每个bean。同样,这是常见的Spring自动装配规则。
每当您想要更改实现时,只需使用新实现的bean名称调用updateImplementations
方法,该名称是类的小写驼峰命名法简单名称,即对于MyImplA
和MyImplB
类,分别是myImplA
或myImplB
。
您还应该在启动时调用此方法,以便在实现MyInterfaceReloader
接口的每个bean上设置初始实现。