我是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。
答案 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关系:
由于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")"
]
当所有三个引用都可用时,该组件将被激活,您将获得一个包含三个项目的字典服务数组(该数组与配置中的顺序相同)。