我的Liferay服务实现模块的两个实例

Two instances of my Liferay service implementation module

我写了一个定义服务的模块:

public interface TranslationService {
    // a method.
}

...实现服务的模块:

@Component(
    immediate = true,
    configurationPid = "my.TranslationConfiguration"
)
public class TranslationServiceImpl implements TranslationService {

    log.info("Constructor " + getClass().getName()
             + " " + System.identityHashCode(this));

    @Activate
    @Modified
    protected void activate(Map<String, Object> properties) {
        log.info("Configuring " + translationService.getClass().getName()
                 + " " + System.identityHashCode(this));
        configuration = ConfigurableUtil.createConfigurable(
            TranslationConfiguration.class, properties);
}

    // an implementation of the method.
}

及其服务激活器:

public class ServiceActivator implements BundleActivator {

    private ServiceRegistration registration;

    @Override
    public void start(BundleContext context) throws Exception {
        registration = context.registerService(TranslationService.class.getName(), new TranslationServiceImpl(), null);
    }

    [...]
}

...和一个使用该服务的模块:

@Component(
    immediate = true,
    configurationPid = [...]
    service = Portlet.class
)
public class TranslationPortlet extends MVCPortlet {

    @Reference(unbind = "-")
    protected void setTranslationService(TranslationService translationService) {
        log.info("Using " + translationService.getClass().getName()
               + " " + System.identityHashCode(translationService));
        this.translationService = translationService;
    }

    private TranslationService translationService;
}

日志

在我启动时记录(通过 Gogo Shell)API 和实现模块:

Constructor my.TranslationServiceImpl 606817095
Service registered.
STARTED my.impl_1.0.0 [538]
Constructor my.TranslationServiceImpl 362465287
Configuring my.TranslationServiceImpl 362465287

然后当我开始(通过 Gogo Shell)使用模块时:

STARTED my.app_1.0.0 [558]
Using my.TranslationServiceImpl 606817095

问题

为什么要使用两个实例?
如何让所有模块使用我的服务实现的同一个实例?

详情

两次调用实现构造函数的堆栈跟踪:

my.TranslationServiceImpl.<init>(TranslationServiceImpl.java:56)
my.ServiceActivator.start(ServiceActivator.java:24)
org.eclipse.osgi.internal.framework.BundleContextImpl.run(BundleContextImpl.java:774)
org.eclipse.osgi.internal.framework.BundleContextImpl.run(BundleContextImpl.java:1)
java.security.AccessController.doPrivileged(Native Method)
org.eclipse.osgi.internal.framework.BundleContextImpl.startActivator(BundleContextImpl.java:767)
org.eclipse.osgi.internal.framework.BundleContextImpl.start(BundleContextImpl.java:724)
org.eclipse.osgi.internal.framework.EquinoxBundle.startWorker0(EquinoxBundle.java:951)
org.eclipse.osgi.internal.framework.EquinoxBundle$EquinoxModule.startWorker(EquinoxBundle.java:328)
org.eclipse.osgi.container.Module.doStart(Module.java:566)
org.eclipse.osgi.container.Module.start(Module.java:434)
org.eclipse.osgi.internal.framework.EquinoxBundle.start(EquinoxBundle.java:402)
org.apache.felix.gogo.command.Basic.start(Basic.java:729)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:498)
org.apache.felix.gogo.runtime.Reflective.invoke(Reflective.java:137)

然后:

my.TranslationServiceImpl.<init>(TranslationServiceImpl.java:56)
sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
java.lang.reflect.Constructor.newInstance(Constructor.java:423)
java.lang.Class.newInstance(Class.java:442)
org.apache.felix.scr.impl.manager.SingleComponentManager.createImplementationObject(SingleComponentManager.java:236)
org.apache.felix.scr.impl.manager.SingleComponentManager.createComponent(SingleComponentManager.java:108)
org.apache.felix.scr.impl.manager.SingleComponentManager.getService(SingleComponentManager.java:906)
org.apache.felix.scr.impl.manager.SingleComponentManager.getServiceInternal(SingleComponentManager.java:879)
org.apache.felix.scr.impl.manager.AbstractComponentManager.activateInternal(AbstractComponentManager.java:748)
org.apache.felix.scr.impl.manager.AbstractComponentManager.enableInternal(AbstractComponentManager.java:674)
org.apache.felix.scr.impl.manager.AbstractComponentManager.enable(AbstractComponentManager.java:429)
org.apache.felix.scr.impl.manager.ConfigurableComponentHolder.enableComponents(ConfigurableComponentHolder.java:657)
org.apache.felix.scr.impl.BundleComponentActivator.initialEnable(BundleComponentActivator.java:341)
org.apache.felix.scr.impl.Activator.loadComponents(Activator.java:403)
org.apache.felix.scr.impl.Activator.access0(Activator.java:54)
org.apache.felix.scr.impl.Activator$ScrExtension.start(Activator.java:278)
org.apache.felix.utils.extender.AbstractExtender.createExtension(AbstractExtender.java:259)
org.apache.felix.utils.extender.AbstractExtender.modifiedBundle(AbstractExtender.java:232)
org.osgi.util.tracker.BundleTracker$Tracked.customizerModified(BundleTracker.java:482)
org.osgi.util.tracker.BundleTracker$Tracked.customizerModified(BundleTracker.java:1)
org.osgi.util.tracker.AbstractTracked.track(AbstractTracked.java:232)
org.osgi.util.tracker.BundleTracker$Tracked.bundleChanged(BundleTracker.java:444)
org.eclipse.osgi.internal.framework.BundleContextImpl.dispatchEvent(BundleContextImpl.java:905)
org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:230)
org.eclipse.osgi.framework.eventmgr.ListenerQueue.dispatchEventSynchronous(ListenerQueue.java:148)
org.eclipse.osgi.internal.framework.EquinoxEventPublisher.publishBundleEventPrivileged(EquinoxEventPublisher.java:165)
org.eclipse.osgi.internal.framework.EquinoxEventPublisher.publishBundleEvent(EquinoxEventPublisher.java:75)
org.eclipse.osgi.internal.framework.EquinoxEventPublisher.publishBundleEvent(EquinoxEventPublisher.java:67)
org.eclipse.osgi.internal.framework.EquinoxContainerAdaptor.publishModuleEvent(EquinoxContainerAdaptor.java:102)
org.eclipse.osgi.container.Module.publishEvent(Module.java:461)
org.eclipse.osgi.container.Module.start(Module.java:452)
org.eclipse.osgi.internal.framework.EquinoxBundle.start(EquinoxBundle.java:402)
org.apache.felix.gogo.command.Basic.start(Basic.java:729)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:498)
org.apache.felix.gogo.runtime.Reflective.invoke(Reflective.java:137)

所以我似乎不应该在服务激活器中实例化实现?不过 this tutorial 似乎就是这样做的。

您正在获取组件的第二个实例,因为您在 ServiceActivator.java 的第 24 行明确创建了它。您发布的第一个堆栈跟踪揭示了这一点。

干脆把那行代码去掉,就万事大吉了。

事实上,您可能根本不需要 ServiceActivator。我假设它是您的捆绑包的 BundleActivator?使用 DS 时不需要编写激活器,因为在激活器中可以做的任何事情都可以在 DS 组件中完成,而且还有更多。事实上,我的一般建议是永远不要编写捆绑激活器。

Neil 已经找到了答案,让我补充一些细节:

正如 Neil 所说,不需要服务激活器,尽管建议我在问题中链接到的教程。删掉就行了。

显然,还要从 bnd.bnd 中删除 Bundle-Activator: 行,否则部署时会出现 Bundle-Activator not found on the bundle class path nor in imports 错误。

使用 Neil 所说的 "DS" 代替服务激活器。它意味着声明式服务,是一种与注释一起工作的 OSGi 技术。要使用它,只需在实现 class 的 @Component 注释中添加一条 service = 指令,并将已实现的服务 (API) 的接口作为值。示例:

@Component(
    service = TranslationService.class,
    immediate = true,
    configurationPid = "my.TranslationConfiguration"
)
public class TranslationServiceImpl implements TranslationService {