【问题标题】:Spring: how to change interface implementations at runtimeSpring:如何在运行时更改接口实现
【发布时间】:2017-10-15 11:43:55
【问题描述】:

作为一名 Java 开发人员,我经常需要在我的接口的不同实现之间进行选择。有时这种选择可以一次完成,而有时我需要不同的实现来响应我的程序接收到的不同输入。换句话说,我需要能够在运行时更改 实现。这很容易通过一个帮助对象来实现,该对象将一些键(基于用户输入)转换为对合适接口实现的引用。

使用 Spring,我可以将这样的对象设计为 bean,并将其注入到我需要的任何地方:

public class MyClass {

    @Autowired
    private MyHelper helper;

    public void someMethod(String someKey) {
        AnInterface i = helper.giveMeTheRightImplementation(someKey);
        i.doYourjob();
    }

}

现在,我应该如何实现帮助程序?让我们从这个开始:

@Service
public class MyHelper {

    public AnInterface giveMeTheRightImplementation(String key) {
        if (key.equals("foo")) return new Foo();
        else if (key.equals("bar")) return new Bar();
        else ...
    }

}

这样的解决方案有几个缺陷。最糟糕的情况之一是容器不知道从帮助程序返回的实例,因此无法从依赖注入中受益。换句话说,即使我这样定义Foo 类:

@Service
public class Foo {

    @Autowired
    private VeryCoolService coolService;

    ...

}

...MyHelper 返回的 Foo 实例不会正确初始化 coolService 字段。

为避免这种情况,经常建议的解决方法是在帮助器中注入每个可能的实现

@Service
public class MyHelper {

    @Autowired
    private Foo foo;

    @Autowired
    private Bar bar;

    ...

    public AnInterface giveMeTheRightImplementation(String key) {
        if (key.equals("foo")) return foo;
        else if (key.equals("bar")) return bar;
        else ...
    }

}

但我不喜欢这种解决方案。我发现像这样更优雅和可维护的东西:

@Service
public class MyHelper {

    @Autowired
    private ApplicationContext app;

    public AnInterface giveMeTheRightImplementation(String key) {
        return (AnInterface) app.getBean(key);
    }

}

这是基于 Spring 的ApplicationContext

类似的解决方案是使用ServiceLocatorFactoryBean 类:

public interface MyHelper {

    AnInterface giveMeTheRightImplementation(String key);

}

// Somewhere else, in Java config

@Bean
ServiceLocatorFactoryBean myHelper() {
    ServiceLocatorFactoryBean bean = new ServiceLocatorFactoryBean();
    bean.setServiceLocatorInterface(MyHelper.class);
    return bean;
}

但由于我不是 Spring 专家,我想知道是否有更好的方法。

【问题讨论】:

  • 你可以在你的方法中传递一个类而不是一个字符串,比如Foo.class
  • @Nathan 我不确定你的提议。请注意,选择正确的课程是问题,而不是解决方案。
  • 一定要按键查找吗?如果您的解决方案只是解决该问题以区分同一接口的实现,那么使用 @Qualifier 自动装配并按名称引用 bean 可能会对您有所帮助。
  • @msparer 目标是能够将某种输入转化为合适的实现。在我的示例中,我使用了一个名为 someKey 的字符串,它被视为 bean 名称,但不一定是这种情况。它甚至可以是一个数字,并且可以根据这个数字的大小来选择实现,其中一些实现对小数字更有效,而另一些对更大的数字更有效。此外,@Qualifier 也无济于事,因为它可以让您选择特定的 bean,但是一旦连接好,您就无法切换到另一个 :-/
  • 看来问题和this类似

标签: java spring dependency-injection interface polymorphism


【解决方案1】:

我在我的项目中采用这种方法。这不是万无一失的,但它在使用非常少的配置代码添加新实现方面非常有用。

我用这样的东西创建了一个枚举

enum Mapper{
    KEY1("key1", "foo"),
    KEY2("key2", "bar")
    ;

    private String key;
    private String beanName;

    public static getBeanNameForKey(String key){
       // traverse through enums using values() and return beanName for key
    }
}

让我们假设 Foo 和 Bar 都从一个命令接口实现。我们称它为接口

class ImplFactory{

    @Autowired
    Map<String, AnInterface> implMap; // This will autowire all the implementations of AnInterface with the bean name as the key

    public AnInterface getImpl(string beanName){
            implMap.get(beanName);
    }
  }

你的助手类看起来像这样

@Service
public class MyHelper {

@Autowired
ImplFactory factory;

    public AnInterface giveMeTheRightImplementation(String key) {

        String beanName = Mapper.getBeanNameForKey(key);  
        factory.getImpl(beanName);
    }  
}

这种方法的一些优点是,
1.在选择正确的实现时避免了冗长的if else's or switch case。
2. 如果你想添加一个新的实现。您所要做的就是在枚举中添加一个 Mapper(除了添加新的 Impl 类)。
3. 您甚至可以为您想要的 impl 类配置 Bean 名称(如果您不想要 spring 给出的默认 bean 名称)。这些名称将是您工厂类中映射的键。您必须在枚举中使用它。

编辑: 如果您希望为您的 bean 提供一个自定义名称,您可以使用其中一个原型注释的 value 属性。例如。如果您已将 Impl 注释为 @Component 或 @Service,则执行 @Component("myBeanName1")@Service("myBeanName2")

【讨论】:

  • 如果您希望密钥不是 bean 名称,您可以在 setter 上使用 @autowire 并根据 bean 属性之一创建密钥。
  • 将字符串映射到 bean 名称对于简单的场景来说很好,但更一般地说,选择正确(甚至是 最佳)实现所需的逻辑可能很复杂。当然,这个逻辑应该封装在一个专用对象中(比如class MyHelper)。当然,这个对象可以(有时应该)与其他对象协作(这基本上就是您对enum Mapperclass ImplFactory 的建议)。但这些对我来说只是实现细节。我的实际目标是让框架更清楚我想要做什么。
【解决方案2】:

做你想做的事的标准方式应该是这样的:

interface YourInterface {
    void doSomething();
}

public class YourClass {

    @Inject @Any Instance<YourInterface> anImplementation;

    public void yourMethod(String someInput) {
        Annotation qualifier = turnInputIntoQualifier(someInput);
        anImplementation.select(qualifier).get().doSomething();
    }

    private Annotation turnInputIntoQualifier(String input) {
        ...
    }

}

然而,目前 Spring does not support it(虽然 它计划用于 v5.x)。它应该工作 application servers.

如果您想坚持使用 Spring,请使用基于 ServiceLocatorFactoryBean 的解决方案 可能是最好的。

【讨论】:

    【解决方案3】:

    您可以在声明 bean 时为其命名,并且您的助手可以要求应用程序上下文返回给定类型的 bean。根据 bean 上声明的范围,如果您需要或重用单例或其他基于上下文可用的范围,应用程序上下文可以创建一个新实例。这样您就可以充分利用弹簧功能。

    例如

    @Service
    public class MyHelper {
       @Autowired
       ApplicationContext applicationContext;
    
       public AnInterface giveMeTheRightImplementation(String key) {
    
       return context.getBean(key);
    }
    
    }
    
    @Service("foo")
    public class Foo implements AnInterface {
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-05-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-15
      • 1970-01-01
      相关资源
      最近更新 更多