使用声明式服务基于属性动态选择OSGi引用

时间:2019-04-12 13:56:49

标签: java dynamic osgi declarative-services

我是OSGi的新手,但很快就被它的复杂性所淹没。我认为这应该很简单,但是我无法找到我想要达到的目标的完整工作示例。

我有一个Java类Foo,其中包含服务集合。这些服务需要基于特定于Foo特定实例的值进行过滤。 Foo可以有多个实例,但是每个实例都应该有自己的一组过滤服务。

为说明起见,请考虑以下示例(受Apache Felix tutorials的启发):

public interface DictionaryService {
    public boolean check(String word);
}
@Component(property = "language=en")
public class EnglishDictionaryService implements DictionaryService {
    private static final String[] WORDS = {"hi", "hello" /*...*/};

    @Override
    public boolean check(String word) {
        if (word == null || word.isEmpty()) {
            return true;
        }

        // super inefficient but you get the gist
        return Arrays.stream(WORDS).anyMatch(entry -> word.equalsIgnoreCase(entry));
    }
}
@Component(property = "language=en")
public class TexanDictionaryService implements DictionaryService {
    private static final String[] WORDS = {"howdy" /*...*/};
    //...
}
@Component(property = "language=en")
public class AustralianDictionaryService implements DictionaryService {
    private static final String[] WORDS = {"g'day" /*...*/};
    //...
}
@Component(property = "language=es")
public class SpanishDictionaryService implements DictionaryService {
    private static final String[] WORDS = {"hola" /*...*/};
    //...
}
@Component
public class SpellChecker {

    @Reference
    private volatile List<DictionaryService> dictionaryServices;

    public SpellChecker(String language) {
        // TODO: how to ensure my dictionaryServices match the given language code?
        // dictionaryServices.target = "(language=" + language + ")"
    }

    public boolean check(String word) {
        if (word == null || word.isEmpty()) {
            return true;
        }

        List<DictionaryService> ds = dictionaryServices;

        if (ds == null || ds.isEmpty()) {
            return false;
        }

        return ds.stream().anyMatch(dictionary -> dictionary.check(word));
    }
}
public static void main(String[] args) {
    SpellChecker englishChecker = new SpellChecker("en");
    SpellChecker spanishChecker = new SpellChecker("es");
    // do stuff
}

在通读several StackExchange posts和一些other articles之后,似乎可以使用ConfigurationAdmin完成此操作。但是,尚不清楚应在何处以及如何使用ConfigurationAdmin,尤其是在声明式服务方面。我也阅读并重读了Configuration Admin Service Specification,但是我在努力应用这些概念方面很挣扎。

有人可以填补我的理解空白吗?

谢谢!


编辑

Christian's answer帮助我以不同的方式考虑了声明式服务。在进行研究时,我再次遇到了Alan Hohn's blog posts on DZone。不幸的是,他似乎从未完成过他的系列,该系列承诺将使用DS进行服务查找。但是,他的example source code包含以下内容:

public String greet(String language) {
    BundleContext context = FrameworkUtil.getBundle(this.getClass()).getBundleContext();

    String filter = "(language=" + language + ")";

    // Get ServiceReference(s) from OSGi framework, filtered for the specified value
    ServiceReference[] refs = null;
    try {
        refs = context.getServiceReferences(Greeter.class.getName(), filter);
        if (null != refs) {
            Greeter greeter = (Greeter)context.getService(refs[0]);
            return greeter.greet();
        }
    } catch (InvalidSyntaxException e) {
        LOGGER.error("Invalid query syntax", e);
    }
    LOGGER.warn("No plugins found, making the default greeting");
    return "Hello from the greeter manager!";
}

这看起来是可行的解决方案,但似乎没有使用DS。这种方法是否有特殊考虑?我在SO和其他地方看到过很多帖子,声称DS是BundleContext#getServiceReferences的万灵药,所以我很好奇是否/可以重构为使用DS。

2 个答案:

答案 0 :(得分:1)

您的主要代码没有意义。

如果使用 new 关键字创建(声明性服务)DS组件的实例,则将不会执行整个DS逻辑。实际上,在OSGi中,您根本不使用main方法……也许是为了启动框架,而不是为了您自己的逻辑。

您可以通过创建使用它的shell命令或创建使用它的http白板服务来访问拼写检查器。

要为SpellChecker中的服务引用设置过滤器,可以使用类似以下的配置:

pid:SpellChecker的标准名称


dictionaryServices.target=(language=en)

这会将SpellChecker设置为仅使用英语词典。

有关DS的更多提示,请参阅 https://liquid-reality.de/2016/09/26/hints-ds.html

答案 1 :(得分:0)

据我了解,您希望两个组件之间具有1:N的关系。

使用DS,您可以选择两种选择:

白板图案

您可以实现白板模式,其中组件 1 跟踪DictionaryService OSGi服务的服务注册。组件 N 注册服务,并且每个服务注册都由注册的组件 1 捕获和使用。

问题可能是您不想在生产中激活组件 1 ,直到所有预期的组件 N 注册了它们的服务并被 1 跟踪

使用具有复杂过滤器表达式的多基数引用

您在以下配置中使用具有多个基数和过滤器表达式的引用:(|(language = en)(language = es))

问题与白板图案相同。

许多人开始编写其中还定义了1:N关系的“运行状况检查器”,并在未启动所有服务时通知程序员(或不允许用户访问该应用程序)。运行状况检查器方法的问题在于,程序员必须在系统中具有相同的逻辑冗余。

使用ECM(OSGi的另一个组件模型)代替DS

虽然DS多基数参考和白板模式在开发时提供了非常舒适的灵活性,但是当必须在用户变得可访问应用程序之前注入所有服务时,它通常不适合生产。

ECM通过以下方式支持1:N关系:

  • 在组件 1 中,您可以定义一个过滤器数组
  • 仅当所有指定过滤器均具有OSGi服务时,
  • 组件 1 才得到满足。

由于ECM组件的范围和目标与DS组件的范围和目标非常相似,因此人们知道DS只需几个小时即可学习ECM。由于ECM也依赖于OSGi服务,因此DS和ECM可以轻松地在同一系统中彼此相邻并使用由另一个提供的OSGi服务。

根据您的示例:

// All annotations from the ecm package

@Component
public class SpellChecker {

  @ServiceRef
  private DictionaryService[] dictionaryServices;

  // I think the language should be a parameter of your service function
  // rather than a member variable of your component class
  public boolean check(String word, String language) {
    if (word == null || word.isEmpty()) {
      return true;
    }

    if (dictionaryServices == null || dictionaryService.length = 0) {
      return false;
    }

    List<DictionaryService> ds = Arrays.asList(dictionaryServices);

    return ds.stream().anyMatch(dictionary -> dictionary.check(word));
  }

  // You need a setter in case of ECM and you can annotate the setter as well.
  // If you annotate the field instead, you need to specify the setter as an
  // attribute of the annotation
  @ServiceRef()
  public void setDictionaryServices(DictionaryService[] dictionaryServices) {
    this.dictionaryServices = dictionaryServices;
  }
}

上面的组件可以在配置中与以下字符串数组一起使用:

dictionaryServices.target = [
                             "(language=en)",
                             "(language=de)",
                             "(language="es")"
                            ]

当所有三个引用都可用时,该组件将被激活,您将获得一个包含三个项目的字典服务数组(该数组与配置中的顺序相同)。