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

huangapple go评论62阅读模式
英文:

Injecting singleton OSGi Declarative Service in Eclipse RCP

问题

以下是翻译好的部分:

我试图定义一个单例的OSGi服务,其他Eclipse RCP应用程序中的插件可以使用(共享),但是每个插件都有自己的版本(来自本地类加载器),只有最高排名的那个版本会被注入。

这个服务被定义在com.test.taskmodel bundle中,如下所示(暂时不使用单独的接口和实现):

@Component(scope=ServiceScope.SINGLETON, service=TaskService.class)
public final class TaskService {

	public TaskService() {}

	@Activate
	void activate(BundleContext bundleContext) {
		//激活
	}
	
	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。它们目前都几乎相同,每个插件只有一个如下所示的Part:

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只尝试注入第一个服务,然后在taskfiltertaskdetail插件中注入失败,因为它们使用不同的类加载器,所以服务类型不匹配?为什么选择服务时没有考虑类加载器,而只在注入时考虑?在这种情况下,至少在每个插件中会得到一个单独的实例,这虽然不完全是我想要的,但至少可以用来工作。非常感谢您的帮助。

英文:

I'm trying to define a singleton OSGi service that would be used (=shared) by other plugins in my Eclipse RCP application but every plugin has its own version (from local classloader) and only the one version with the highest ranking gets injected.

The service is defined (in com.test.taskmodel bundle) as such (not using separate interface and implementation for now):

@Component(scope=ServiceScope.SINGLETON, service=TaskService.class)
public final class TaskService {

	public TaskService() {}

	@Activate
	void activate(BundleContext bundleContext) {
		//activate
	}
	
	public Result doStuff() {
		return null;
	}
}

When I build it using bnd, the resulting jar manifest contains Service-Component: OSGI-INF/com.test.TaskService.xml and the xml is:

&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;scr:component xmlns:scr=&quot;http://www.osgi.org/xmlns/scr/v1.3.0&quot; name=&quot;com.test.TaskService&quot; activate=&quot;activate&quot; deactivate=&quot;deactivate&quot;&gt;
  &lt;service scope=&quot;singleton&quot;&gt;
    &lt;provide interface=&quot;com.test.TaskService&quot;/&gt;
  &lt;/service&gt;
  &lt;implementation class=&quot;com.test.TaskService&quot;/&gt;
&lt;/scr:component&gt;

There are three Eclipse RCP plugins using the service: taskbrowser, taskfilter and taskdetail. They are all virtually identical for now, each having only one Part like this:

public class TaskBrowserPart {
	
	@Inject @Service @Optional private TaskService taskService;

	@PostConstruct
	public void createControls(Composite parent) {
		if (taskService == null) {
			System.out.println(&quot;TaskBrowser TaskService null&quot;);
		} else {
			System.out.println(&quot;TaskBrowser TaskService non-null&quot;);
		}
	}
}

When I run the application I get the following output:

TaskBrowser TaskService non-null
TaskFilter TaskService null
TaskDetail TaskService null

Calling service command in the osgi console shows four registered services with only the first one being used by all the tree plugins:

{com.test.taskmodel.TaskService}={service.id=63, service.bundleid=155, service.scope=bundle, component.name=com.test.taskmodel.TaskService, component.id=34}
  &quot;Registered by bundle:&quot; com.test.taskbrowser [155]
  &quot;Bundles using service&quot;
    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}
  &quot;Registered by bundle:&quot; com.test.taskdetail [158]
  &quot;No bundles using service.&quot;
{com.test.taskmodel.TaskService}={service.id=66, service.bundleid=159, service.scope=bundle, component.name=com.test.taskmodel.TaskService, component.id=37}
  &quot;Registered by bundle:&quot; com.test.taskmodel [159]
  &quot;No bundles using service.&quot;
{com.test.taskmodel.TaskService}={service.id=87, service.bundleid=165, service.scope=bundle, component.name=com.test.taskmodel.TaskService, component.id=39}
  &quot;Registered by bundle:&quot; com.test.taskfilter [165]
  &quot;No bundles using service.&quot;

Why are there multiple service instances with service.scope=bundle when I have the service defined with scope=&quot;singleton&quot;? How do I define it in a way that only one instance of the service exists and all plugins use it? Why does Eclipse try to inject only the first service and then fails to do so in the taskfilter and taskdetail plugins because they use different classloaders and therefore the service types do not match? Why is the classloader not taken into account when selecting the service but only when injecting it? In that case I'd at least got a separate instance in every plugin, which is not what I want exactly but at least something I could work with. Any help is appreciated, thank you.

答案1

得分: 1

你要尝试实现的实际上是Declarative Services的默认行为。您不应该需要指定ServiceScope.SINGLETON属性,也不应该在这里使用额外的@Service注解(尽管这两者都不应该引起问题)。

这是否发生在使用工具创建DS XML文件的Eclipse IDE本身内?我注意到您在问题中提到了使用Bnd。您是否看到任何旧的XML文件或Service-Component条目?或者OSGi缓存中可能存在旧数据吗?

实际上,您不应该看到TaskBrowser、TaskDetail和TaskFilter bundle创建TaskService,只要Service-Component条目仅位于TaskModel bundle中。

英文:

What you're trying to accomplish is actually the default behavior for Declarative Services. You shouldn't need to specify the ServiceScope.SINGLETON property and you also shouldn't need to use the additional @Service annotation here (though neither of these should cause a problem).

Does this occur within the Eclipse IDE itself using the tooling to create the DS XML files? I notice you reference using Bnd in the question. Do you see any old XML files or Service-Component entries laying around? Or is it possible there could be stale data in the OSGi cache?

You really shouldn't be seeing the TaskBrowser, TaskDetail, and TaskFilter bundles creating the TaskService, assuming the Service-Component entry is only in the TaskModel bundle.

答案2

得分: 0

看起来你在所有的bundle中都声明了这个服务,导致每个bundle都有它们自己的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]

Registered 意味着它“提交”了一个类定义(不是创建了一个实例)。

当你在Bnd中使用广泛的选择器来定义jar内容时,就会发生这种情况,比如

-private-package: com.test.*
Private-Package: com.test.*
Export-Package: com.test.*

导致com.test.taskservice被随处包含。在这种情况下,DS会在每个bundle中找到你的声明,并创建相同的scr:component xml,彼此竞争。

请参阅 https://bnd.bndtools.org/heads/private_package.html

> Bnd遍历类路径上的软件包,并根据Export-Package和Private-Package头提供的指令将它们复制到输出中。

英文:

It looks like you are declaring the service in all bundles causing each to have their own implementation of TaskService. These are then incompatible with each other, so when first instance is returned to non-owner, it is from different class loader.

As @Patrick Paulin mentions in his answer, you can see that in your services printout

&quot;Registered by bundle:&quot; com.test.taskbrowser [155]
&quot;Registered by bundle:&quot; com.test.taskdetail [158]
&quot;Registered by bundle:&quot; com.test.taskmodel [159]
&quot;Registered by bundle:&quot; com.test.taskfilter [165]

Registered meaning a class definition was "submitted" by it (not that it created an instance).

It can happen in Bnd when you define jar contents with broad selectors such as

-private-package: com.test.*
Private-Package: com.test.*
Export-Package: com.test.*

resulting in com.test.taskservice being included everywhere. There DS finds your declaration and creates identical scr:component xml in every bundle, competing with each other.

See 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.

huangapple
  • 本文由 发表于 2020年9月19日 02:18:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/63960891.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定