在 Eclipse RCP 中注入单例 OSGi 声明式服务
Injecting singleton OSGi Declarative Service in Eclipse RCP
我正在尝试定义一个单例 OSGi 服务,该服务将由我的 Eclipse RCP 应用程序中的其他插件使用(=共享),但每个插件都有自己的 版本(来自本地类加载器)并且只有排名最高的一个版本被注入。
服务是这样定义的(在 com.test.taskmodel
包中)(现在不使用单独的接口和实现):
@Component(scope=ServiceScope.SINGLETON, service=TaskService.class)
public final class TaskService {
public TaskService() {}
@Activate
void activate(BundleContext bundleContext) {
//activate
}
public Result doStuff() {
return null;
}
}
当我使用 bnd 构建它时,生成的 jar 清单包含 Service-Component: OSGI-INF/com.test.TaskService.xml
而 xml 是:
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.3.0" name="com.test.TaskService" activate="activate" deactivate="deactivate">
<service scope="singleton">
<provide interface="com.test.TaskService"/>
</service>
<implementation class="com.test.TaskService"/>
</scr:component>
有三个 Eclipse RCP 插件使用该服务:taskbrowser
、taskfilter
和 taskdetail
。它们现在几乎完全相同,每个只有一个部分,如下所示:
public class TaskBrowserPart {
@Inject @Service @Optional private TaskService taskService;
@PostConstruct
public void createControls(Composite parent) {
if (taskService == null) {
System.out.println("TaskBrowser TaskService null");
} else {
System.out.println("TaskBrowser TaskService non-null");
}
}
}
当我 运行 应用程序时,我得到以下输出:
TaskBrowser TaskService non-null
TaskFilter TaskService null
TaskDetail TaskService null
在 osgi 控制台中调用 service
命令显示了四个已注册的服务,只有第一个被所有树插件使用:
{com.test.taskmodel.TaskService}={service.id=63, service.bundleid=155, service.scope=bundle, component.name=com.test.taskmodel.TaskService, component.id=34}
"Registered by bundle:" com.test.taskbrowser [155]
"Bundles using service"
com.test.taskfilter [165]
com.test.taskdetail [158]
com.test.taskbrowser [155]
{com.test.taskmodel.TaskService}={service.id=65, service.bundleid=158, service.scope=bundle, component.name=com.test.taskmodel.TaskService, component.id=36}
"Registered by bundle:" com.test.taskdetail [158]
"No bundles using service."
{com.test.taskmodel.TaskService}={service.id=66, service.bundleid=159, service.scope=bundle, component.name=com.test.taskmodel.TaskService, component.id=37}
"Registered by bundle:" com.test.taskmodel [159]
"No bundles using service."
{com.test.taskmodel.TaskService}={service.id=87, service.bundleid=165, service.scope=bundle, component.name=com.test.taskmodel.TaskService, component.id=39}
"Registered by bundle:" com.test.taskfilter [165]
"No bundles using service."
当我定义了 scope="singleton"
的服务时,为什么会有多个 service.scope=bundle
的服务实例?如何以仅存在一个服务实例并且所有插件都使用它的方式定义它?为什么 Eclipse 只尝试注入第一个服务,然后在 taskfilter
和 taskdetail
插件中却失败了,因为它们使用不同的类加载器,因此服务类型不匹配?为什么在选择服务时不考虑类加载器,而只在注入服务时考虑?在那种情况下,我至少在每个插件中都有一个单独的实例,这不是我想要的,但至少是我可以使用的。感谢任何帮助,谢谢。
您要完成的实际上是声明式服务的默认行为。您不需要指定 ServiceScope.SINGLETON 属性 并且您也不应该在这里使用额外的 @Service 注释(尽管这些都不会导致问题)。
这是否发生在使用工具创建 DS XML 文件的 Eclipse IDE 本身中?我注意到您在问题中提到了使用 Bnd。您是否看到任何旧的 XML 文件或 Service-Component 条目?或者 OSGi 缓存中是否可能存在陈旧数据?
假设 Service-Component 条目仅在 TaskModel 包中,您真的不应该看到创建 TaskService 的 TaskBrowser、TaskDetail 和 TaskFilter 包。
您似乎在所有包中声明服务,导致每个包都有自己的 TaskService 实现。然后它们彼此不兼容,因此当第一个实例返回到 non-owner 时,它来自不同的 class 加载器。
正如@Patrick Paulin 在他的回答中提到的那样,您可以在服务打印输出中看到这一点
"Registered by bundle:" com.test.taskbrowser [155]
"Registered by bundle:" com.test.taskdetail [158]
"Registered by bundle:" com.test.taskmodel [159]
"Registered by bundle:" com.test.taskfilter [165]
Registered 意味着 class 定义被它“提交”了(不是它创建了一个实例)。
当您使用广泛的选择器定义 jar 内容时,它可能会在 Bnd 中发生,例如
-private-package: com.test.*
Private-Package: com.test.*
Export-Package: com.test.*
导致 com.test.taskservice 被包含在所有地方。 DS 找到您的声明并在每个包中创建相同的 scr:component
xml,相互竞争。
见https://bnd.bndtools.org/heads/private_package.html
Bnd traverse the packages on the classpath and copies them to the output based on the instructions given by the Export-Package and Private-Package headers.
我正在尝试定义一个单例 OSGi 服务,该服务将由我的 Eclipse RCP 应用程序中的其他插件使用(=共享),但每个插件都有自己的 版本(来自本地类加载器)并且只有排名最高的一个版本被注入。
服务是这样定义的(在 com.test.taskmodel
包中)(现在不使用单独的接口和实现):
@Component(scope=ServiceScope.SINGLETON, service=TaskService.class)
public final class TaskService {
public TaskService() {}
@Activate
void activate(BundleContext bundleContext) {
//activate
}
public Result doStuff() {
return null;
}
}
当我使用 bnd 构建它时,生成的 jar 清单包含 Service-Component: OSGI-INF/com.test.TaskService.xml
而 xml 是:
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.3.0" name="com.test.TaskService" activate="activate" deactivate="deactivate">
<service scope="singleton">
<provide interface="com.test.TaskService"/>
</service>
<implementation class="com.test.TaskService"/>
</scr:component>
有三个 Eclipse RCP 插件使用该服务:taskbrowser
、taskfilter
和 taskdetail
。它们现在几乎完全相同,每个只有一个部分,如下所示:
public class TaskBrowserPart {
@Inject @Service @Optional private TaskService taskService;
@PostConstruct
public void createControls(Composite parent) {
if (taskService == null) {
System.out.println("TaskBrowser TaskService null");
} else {
System.out.println("TaskBrowser TaskService non-null");
}
}
}
当我 运行 应用程序时,我得到以下输出:
TaskBrowser TaskService non-null
TaskFilter TaskService null
TaskDetail TaskService null
在 osgi 控制台中调用 service
命令显示了四个已注册的服务,只有第一个被所有树插件使用:
{com.test.taskmodel.TaskService}={service.id=63, service.bundleid=155, service.scope=bundle, component.name=com.test.taskmodel.TaskService, component.id=34}
"Registered by bundle:" com.test.taskbrowser [155]
"Bundles using service"
com.test.taskfilter [165]
com.test.taskdetail [158]
com.test.taskbrowser [155]
{com.test.taskmodel.TaskService}={service.id=65, service.bundleid=158, service.scope=bundle, component.name=com.test.taskmodel.TaskService, component.id=36}
"Registered by bundle:" com.test.taskdetail [158]
"No bundles using service."
{com.test.taskmodel.TaskService}={service.id=66, service.bundleid=159, service.scope=bundle, component.name=com.test.taskmodel.TaskService, component.id=37}
"Registered by bundle:" com.test.taskmodel [159]
"No bundles using service."
{com.test.taskmodel.TaskService}={service.id=87, service.bundleid=165, service.scope=bundle, component.name=com.test.taskmodel.TaskService, component.id=39}
"Registered by bundle:" com.test.taskfilter [165]
"No bundles using service."
当我定义了 scope="singleton"
的服务时,为什么会有多个 service.scope=bundle
的服务实例?如何以仅存在一个服务实例并且所有插件都使用它的方式定义它?为什么 Eclipse 只尝试注入第一个服务,然后在 taskfilter
和 taskdetail
插件中却失败了,因为它们使用不同的类加载器,因此服务类型不匹配?为什么在选择服务时不考虑类加载器,而只在注入服务时考虑?在那种情况下,我至少在每个插件中都有一个单独的实例,这不是我想要的,但至少是我可以使用的。感谢任何帮助,谢谢。
您要完成的实际上是声明式服务的默认行为。您不需要指定 ServiceScope.SINGLETON 属性 并且您也不应该在这里使用额外的 @Service 注释(尽管这些都不会导致问题)。
这是否发生在使用工具创建 DS XML 文件的 Eclipse IDE 本身中?我注意到您在问题中提到了使用 Bnd。您是否看到任何旧的 XML 文件或 Service-Component 条目?或者 OSGi 缓存中是否可能存在陈旧数据?
假设 Service-Component 条目仅在 TaskModel 包中,您真的不应该看到创建 TaskService 的 TaskBrowser、TaskDetail 和 TaskFilter 包。
您似乎在所有包中声明服务,导致每个包都有自己的 TaskService 实现。然后它们彼此不兼容,因此当第一个实例返回到 non-owner 时,它来自不同的 class 加载器。
正如@Patrick Paulin 在他的回答中提到的那样,您可以在服务打印输出中看到这一点
"Registered by bundle:" com.test.taskbrowser [155]
"Registered by bundle:" com.test.taskdetail [158]
"Registered by bundle:" com.test.taskmodel [159]
"Registered by bundle:" com.test.taskfilter [165]
Registered 意味着 class 定义被它“提交”了(不是它创建了一个实例)。
当您使用广泛的选择器定义 jar 内容时,它可能会在 Bnd 中发生,例如
-private-package: com.test.*
Private-Package: com.test.*
Export-Package: com.test.*
导致 com.test.taskservice 被包含在所有地方。 DS 找到您的声明并在每个包中创建相同的 scr:component
xml,相互竞争。
见https://bnd.bndtools.org/heads/private_package.html
Bnd traverse the packages on the classpath and copies them to the output based on the instructions given by the Export-Package and Private-Package headers.