【问题标题】:Dynamically choose OSGi references based on property using Declarative Services使用声明式服务根据属性动态选择 OSGi 引用
【发布时间】:2023-03-25 07:53:01
【问题描述】:

我是 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
}

阅读severalStackExchangeposts和一些otherarticles后,似乎可以使用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。

【问题讨论】:

    标签: java dynamic osgi declarative-services


    【解决方案1】:

    您在 main 中的代码没有意义。

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

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

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

    pid:拼写检查器的完全限定名


    dictionaryServices.target=(language=en)
    

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

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

    【讨论】:

    • 实际上我没有使用 main 方法,但If you create the instance of a (declarative services) DS component with "new" then the whole DS logic will not be executed 可以解释我的困惑。
    • 感谢您让我走上正确的道路。你对我的编辑有什么看法?
    【解决方案2】:

    据我了解,您希望两个组件之间具有 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中你可以定义一个过滤器数组
    • 组件1只有在所有指定过滤器都有OSGi服务时才会满足。

    由于 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")"
                                ]
    

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

    【讨论】:

    • 感谢您向我介绍 ECM!我得去看看。
    猜你喜欢
    • 2012-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-23
    • 2012-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多