在Eclipse RCP中注入单例OSGi声明式服务

问题描述

我正在尝试定义一个单例Osgi服务,该服务将由Eclipse RCP应用程序中的其他插件使用(共享),但是每个插件都有其自己的 version (来自本地类加载器),并且仅排名最高的版本会被注入。

(在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插件taskbrowsertaskfiltertaskdetail。目前,它们几乎都是相同的,每个都只有一个这样的部分:

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,component.id=36}
  "Registered by bundle:" com.test.taskdetail [158]
  "No bundles using service."
{com.test.taskmodel.TaskService}={service.id=66,service.bundleid=159,component.id=37}
  "Registered by bundle:" com.test.taskmodel [159]
  "No bundles using service."
{com.test.taskmodel.TaskService}={service.id=87,service.bundleid=165,component.id=39}
  "Registered by bundle:" com.test.taskfilter [165]
  "No bundles using service."

当我用service.scope=bundle定义服务时,为什么scope="singleton"有多个服务实例?如何以仅存在一个服务实例且所有插件都使用它的方式定义它?为什么Eclipse为什么尝试仅注入第一个服务,然后又在taskfiltertaskdetail插件中注入失败,因为它们使用了不同的类加载器,因此服务类型不匹配?为什么在选择服务时不考虑类加载器,而仅在注入服务时考虑?在那种情况下,我至少在每个插件中都有一个单独的实例,这不是我想要的,但至少是我可以使用的东西。感谢您的任何帮助,谢谢。

解决方法

您要完成的实际上是声明式服务的默认行为。您不需要指定ServiceScope.SINGLETON属性,也不需要在此处使用其他@Service注释(尽管这两个都不会引起问题)。

这是否在使用工具创建DS XML文件的Eclipse IDE本身中发生?我注意到您在问题中使用Bnd进行引用。您看到周围有旧的XML文件或服务组件条目吗?还是OSGi缓存中可能有过时的数据?

假设Service-Component条目仅在TaskModel捆绑包中,那么您真的不应该看到TaskBrowser,TaskDetail和TaskFilter捆绑包创建TaskService。

,

您似乎在所有捆绑软件中都声明了该服务,从而使每个捆绑软件都有自己的TaskService实现。它们之间是不兼容的,因此当第一个实例返回给非所有者时,它是来自不同的类加载器。

@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]

已注册,表示该类定义是“提交”的(不是它创建了实例)。

当您使用广泛的选择器(例如

)定义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

根据在Export-Package和Private-Package标头中给出的指令,遍历类路径上的包并将它们复制到输出中。