动态注入弹簧豆

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

enter image description here

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

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

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

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

  1. 将注入正确的实施。例如,当设置 ImplB 将被注入到使用 MyInterface 的地方时myinterface.type=implB
  2. 春季环境应使用新值进行刷新,并重新注入豆类。

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

有什么想法吗?

更新

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


答案 1

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

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

@Component
public class MyInterfaceImplementationsHolder {

    @Autowired
    private Map<String, MyInterface> implementations;

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

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

现在,只要你需要注入 一个特定的实现,你都可以在接口的帮助下完成: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来实际更改每个Bean中的实现,该bean具有属性:MyInterface

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

这只需自动连接实现接口的所有bean,并使用新实现更新每个bean,该实现从持有者检索并作为参数传递。同样,常见的弹簧自动布线规则。MyInterfaceReloader

每当您希望更改实现时,只需调用具有新实现的bean名称的方法,这是类的下驼峰大小写简单名称,即 或用于类和 .updateImplementationsmyImplAmyImplBMyImplAMyImplB

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


答案 2

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

让车辆维修服务成为一个接口:

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

希望它有帮助。


推荐