如何在非 OSGi portlet 中访问 Liferay 本地服务?

问题描述

在 Osgi @Component Portlet 中,我可以使用 Osgi 声明式服务访问 Liferay 本地服务:

@Reference
private MyExampleLocalService myExampleService;

除了将服务注入我的 portlet 之外,@Reference 还确保 Liferay 在 MyExampleLocalService 可用之前不会启动我的 portlet。

在 Liferay 中,我还可以在 WAR 中部署 portlet,而无需 @Component 或访问声明性服务。我知道我可以使用像 MyExampleLocalServiceUtil.getService() 这样的静态实用程序访问那里的服务,但这会使我的应用程序面临 NullPointerException 的风险,因为我的 portlet 可能会在 MyExampleLocalService 可用之前启动。

我已经尝试使用 @Inject 和/或 @Reference 来注入服务,但是这不起作用,因为这不是 @Component portlet。 @Inject 抛出异常,而 @Reference 没有任何效果

解决方法

Liferay 的本地服务都是 OSGi 服务,因此如果可能,您应该通过 OSGi @ReferenceOSGi + CDI @Reference @Inject(适用于 Portlet 3.0 portlet)获取您的服务依赖项。使用这些注释,OSGi 将管理您的 portlet,在服务可用时启动 portlet 并注入服务,并在服务不可用时关闭 portlet。1但是,如果您不创建 CDI 或基于 OSGi 的 portlet,您不能使用这些首选方法。例如,部署在 WAR 中的旧版 Portlet 2.0 Portlet 和 Spring Portlet 无法使用声明式服务。

这些 portlet 应该在非 OSGi portlet 中访问 Liferay 本地服务,而您应该使用 OSGi ServiceTrackers。 ServiceTrackerwaitForServicegetService 获取服务的方法。如果您想等待服务(超时),您可以在 portlet 初始化完成后随时调用 waitForService。否则,您可以随时致电 getService() 获取服务,或者在服务不可用时致电 null


不要在 portlet 的 ServiceTracker#waitForService 方法中调用 init这会导致间歇性死锁,因为 Liferay 只在一个时间。如果您的 portlet 正在部署并等待 init 中的服务,它可以阻止依赖服务包完成部署。但是 portlet 永远不会完成部署,因为它需要依赖项。最终等待的 portlet 将超时,但我认为这也可能存在错误,因为即使在 portlet 超时等待之后,死锁似乎仍然存在。


以下是获取和使用服务的一些示例代码:

public final class NonOSGiPortletUsingServices implements Portlet {
  private ServiceTracker<MyExampleLocalService,MyExampleLocalService> myExampleServiceTracker;

  @Override
  public void init(final PortletConfig config) throws PortletException {
    // Obtain the current bundle’s OSGi BundleContext from the PortletContext
    // using the magic constant "osgi-bundlecontext"
    final String bundleContextKey = "osgi-bundlecontext";
    final Object bundleContext = config.getPortletContext().getAttribute(bundleContextKey);
    if (bundleContext == null || !(bundleContext instanceof BundleContext)) {
      throw new PortletException(
          "Could not initialize myExampleService. "
              + bundleContextKey
              + " not found in PortletContext.");
    }

    myExampleServiceTracker =
        new ServiceTracker<>((BundleContext) bundleContext,MyExampleLocalService.class,null);
    myExampleServiceTracker.open();
    final MyExampleService myExampleService =
      myExampleServiceTracker.getService();
    if (myExampleService == null) {
        LOGGER.warn("Required service {} not available.",MyExampleService.class.getName());
    }
    super.init(config);
  }

  @Override
  public void destroy() {
    super.destroy();
    myExampleServiceTracker.close();
  }

  @Override
  public void render(final RenderRequest renderRequest,final RenderResponse renderResponse)
      throws IOException,PortletException {
    final MyExampleService myExampleService = myExampleService();
    if (myExampleService == null) {
        renderResponse.getWriter()
          .append("Required service ")
          .append(MyExampleService.class.getName())
          .append(" not available. ")
          .append(this.getClass().getName())
          .append(" unavailable.");
        return;
    }
    // Use myExampleService here...
  }
}

这种获取服务的方法应该适用于任何非 OSGi Liferay Portlet,例如 Portlet 2.0 Portlet、Spring Portlet 和 JSF Portlet(对于 JSF,您可能更喜欢从 BundleContext )。这种方法适用于任何 OSGi 服务,而不仅仅是 Liferay 的本地服务。


  1. OSGi 服务是短暂的,随时可能消失。