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
Inject spring bean dynamically
提问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 个不同实现的接口:
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):
我希望在运行时 - 一旦属性更改,将发生以下情况(无需重新启动服务器):
- The right implementation will be injected. For example when setting
myinterface.type=implB
ImplB will be injected where-ever MyInterface is used - Spring Environmentshould be refreshed with the new values and re-injected as well to beans.
- 将注入正确的实现。例如,当设置
myinterface.type=implB
ImplB 将被注入到任何使用 MyInterface 的地方时 - 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 Sring
TargetSourcemechanism 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 TargetSource
target.
如果是这样,那么您可以尝试结合ProxyFactoryBean来查看Sring
TargetSource机制。关键是代理对象将被注入到使用您的接口的 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 MyInterface
implementations to be Spring beans, and the Spring context could not contains both instances at the same time then you can try to use ServiceLocatorFactoryBeanwith prototype
target beans scope and Conditional
annotation on target implementation classes. This approach can be used instead of MyRegistry
.
此外,如果您想让两个MyInterface
实现都成为 Spring bean,并且 Spring 上下文不能同时包含两个实例,那么您可以尝试使用ServiceLocatorFactoryBean与prototype
目标 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 MyInterface
interface 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 MyInterface
interface. 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 MyInterfaceReloader
interface. 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 MyInterface
as 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 MyInterfaceReloader
interface 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 updateImplementations
method with the name of the bean of the new implementation, which is the lower camel case simple name of the class, i.e. myImplA
or myImplB
for classes MyImplA
and MyImplB
.
每当您想要更改实现时,您应该只updateImplementations
使用新实现的 bean 的名称调用该方法,该名称是类的小驼峰简单名称,即myImplA
或myImplB
对于类MyImplA
和MyImplB
。
You should also invoke this method at startup, so that an initial implementation is set on every bean that implements the MyInterfaceReloader
interface.
您还应该在启动时调用此方法,以便在实现该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