我已经创建了一个包含多个实现的服务,如下所示:
@ProviderType
public interface MyService {
public void printMessage();
}
@Component
public class Foo implements MyService {
public void printMessage() {
System.out.println("foo");
}
}
@Component
public class Bar implements MyService {
public void printMessage() {
System.out.println("bar");
}
}
实际的实现显然有点复杂(我不能只在printMessage
接受参数的情况下进行单个实现!)。服务的使用者需要能够基于属性选择适当的实现 - 为简单起见,我们可以假设我使用的是实现的名称。
现有的非OSGi实现使用的工厂方法是这样的:
public MyService getService(String property) {
switch (property) {
case "ham":
return new Foo();
case "spam":
return new Bar();
}
}
实际的实现更复杂(它基于property
参数的子串匹配)。将此转换为OSGi的最简单方法(对我而言)是将此工厂方法转换为MyService
的静态方法,仅返回必要的过滤字符串,并使消费者在处理繁琐的位置时BundleContext,然后获取和取消服务:
public class MyConsumer {
private BundleContext context;
@Activate
public void activate(BundleContext context) {
this.context = context;
}
public void doStuff() {
Collection<ServiceReference<MyService>> refs = context.getServiceReferences(MyService.class, MyService.getService("ham"));
ServiceReference<MyService> service = refs.iterator().next();
context.getService(service).printMessage();
context.ungetService(service);
}
有没有更好的方法来创建一个返回“服务”的“工厂”,但不要求消费者处理BundleContexts等。
我发现了一些不会完全工作的方法:
@Reference(target = "ham")
(我知道语法错误)允许我选择基于属性的实现,但仅限于编译时。ServiceFactory
允许不同的bundle获取服务的不同实例,但不指定实现。答案 0 :(得分:2)
服务的概念是消费者不应该选择,因为这会使消费者非常不可重复使用。如果将控制权转移到消费者手中,它通常会产生脆弱的系统。消费者应该使用注册的任何内容。
一种反模式是您尝试隐藏服务的实现,但随后需要在您的使用者中使用特定的实现。如果这是您的用例,那么您的实现是公开的,只需将其设置为自己的服务类型即可。它仍然可以注册为常见的MyService类型,当然,您可以在多种类型下注册。
如果您有一个外部选择标准,可以使用哪个标准,例如:打印机,您希望用户决定,然后您只需获取所有内容并将其显示给用户。使用服务ID,您可以使用正确的服务ID。
如果您需要配置应使用哪种服务,则应使用@Reference(target="(selection.criterium=foo)")
。您可以使用Configuration Admin覆盖此选择。这在targets中有解释。您基本上设置了一个属性,其中包含引用名称,后跟.target
到所需的过滤器。