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

How can I access a Liferay local service in a non-OSGi portlet?

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

@Reference
private MyExampleLocalService myExampleService;

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

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

我试过使用 @Inject and/or @Reference 来注入服务,但这不起作用,因为这不是 @Component portlet。 @Inject 抛出异常,@Reference 没有任何效果。

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

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


不要在您的 portlet 的 init 方法中调用 ServiceTracker#waitForService 这会导致 间歇性死锁 因为Liferay 一次只部署一个 bundle/app。如果您的 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 =
        myExampleServiceTracker.getService();
    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 ExternalContext)。此方法适用于任何 OSGi 服务,而不仅仅是 Liferay 的本地服务。


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