我是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。
你在main中的代码没有意义。
如果使用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:N的关系。
使用DS,您有几种选择:
白板图案
您可以实现白板模式,其中组件1跟踪DictionaryService OSGi服务的服务注册。组件N注册服务,并且每个服务注册被注册的组件1捕获和使用。
问题可能是您不希望在生产中激活组件1,直到所有预期组件N都注册其服务并由1跟踪。
使用具有复杂过滤器表达式的多基数引用
您在配置中使用具有多个基数和过滤器表达式的引用,如:(|(language = en)(language = es))
问题与白板模式相同。
许多人开始编写“健康检查器”,其中还定义了1:N关系,并且如果没有启动所有服务(或者禁止用户可访问该应用程序),则通知程序员。运行状况检查方法的问题在于程序员必须在系统中具有相同的逻辑冗余。
而不是DS,使用ECM(OSGi的另一个组件模型)
虽然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")"
]
当所有三个引用都可用时,组件将被激活,您将获得一个包含三个项目的字典服务数组(数组中的顺序与配置中的顺序相同)。