动态注入Spring Bean

13 浏览
0 Comments

动态注入Spring Bean

在Java-Spring web-app中,我希望能动态注入Bean。例如,我有一个包含2种不同实现的接口:

\"enter

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

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

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

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

  1. 正确的实现将被注入。例如,在设置myinterface.type = implB时,在使用MyInterface的任何地方都将注入ImplB。
  2. 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注入?)。

admin 更改状态以发布 2023年5月21日
0
0 Comments

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

希望这会有所帮助。

0
0 Comments

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

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

@Component
public class MyInterfaceImplementationsHolder {
    @Autowired
    private Map implementations;
    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 Map reloaders;
    @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方法,该名称是类的小写驼峰命名法简单名称,即对于MyImplAMyImplB类,分别是myImplAmyImplB

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

0