Spring:如何在运行时更改接口实现

时间:2017-05-16 11:50:15

标签: java spring dependency-injection interface polymorphism

作为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;

    ...

}

... Foo返回的MyHelper个实例未能正确初始化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专家,我想知道是否有更好的方法。

3 个答案:

答案 0 :(得分:5)

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

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

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都是从comman接口实现的。我们称它为AnInterface

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或switch案例  2.如果要添加新实现。你所要做的就是在你的枚举中添加一个Mapper(除了添加新的Impl类)  3.您甚至可以为您想要的impl类配置Bean名称(如果您不想使用spring给出的默认bean名称)。此名称将是工厂类中地图的关键。您必须在枚举中使用它。

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

答案 1 :(得分: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的解决方案 可能是最好的一个。

答案 2 :(得分:0)

您可以在声明bean时命名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 {
}