Spring:如何在运行时更改接口实现
Posted
技术标签:
【中文标题】Spring:如何在运行时更改接口实现【英文标题】:Spring: how to change interface implementations at runtime 【发布时间】: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类似
【参考方案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 Mapper
和class 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
【讨论】:
以上是关于Spring:如何在运行时更改接口实现的主要内容,如果未能解决你的问题,请参考以下文章
企业级spring-boot案例-Spring Boot 启动时的运行方法
企业级spring-boot案例-Spring Boot 启动时的运行方法