Important 7.2 Service Builder Changes

Liferay 7.2 has an updated Service Builder that introduces some changes that we should all be aware of.

From a Dependency Injection perspective, not really much is going to change for us when it comes to consuming the services. Just as we did for 7.0 and 7.1, we will still have a member variable in our @Component for a service instance and then @Reference inject it in. As long as the service is available, we're off to the races...

From a service development perspective, we have some new options and constraints.

The Dependency Injector Choice

The first change is a new attribute on the <service-builder /> tag, "dependency-injector". This new attribute has two possible values:

"spring" - This is the classic dependency injection implementation we all know and love. Spring is used to instantiate your service layer and the beans will be exposed into OSGi for easy @Reference injection.

"ds" - This is a new OSGi-ified rewrite for the service layer which discards Spring and uses regular OSGi Declarative Services to expose the service implementations and handling @Reference injection.

For new Service Builder projects built using the latest Service Builder, the default value for the attribute is going to be "ds". The ramifications of this choice will be discussed below.

If you wish to stick with the classic implementation, it is easy to do. Just set the "dependency-injector" attribute to "spring" and it will stay with Spring. An example for doing this would be:

<service-builder package-path="com.liferay.docs.guestbook" dependency-injector="spring">
    ...
</service-builder>

The DS Injector

The DS injector is the OSGi Declarative Services injector. Using DS, your service components are now registered as components by decorating with the @Component annotation. It also means that you can add dependencies into your components using the @Reference annotation, you don't need to figure out how to use the @ServiceReference or @BeanReference annotations for the Spring-based Dependency Injection or use a ServiceTracker to manually find an OSGi component.

Sounds great, right?

Well, there are some changes that come into play, things you have to watch out for when going down this path...

bnd.bnd Directive

First of all, you have to ensure that the following directive is in your bnd.bnd file:

-dsannotations-options: inherit

For the DS injector, superclasses of your XxxxLocalServiceImpl class will have some @Reference injections. This directive will ensure that OSGi processes all @Reference injections not just in your own XxxxLocalServiceImpl class, but will also process injections on all of the superclasses.

Without this bnd directive, the @Reference injections will not occur on the superclasses, so you may encounter NullPointerExceptions when trying to use those superclass member fields.

Local Service References

In the Spring-based dependency injector, the superclass will have member fields for the local and remote services and persistence services for all of the entities defined in the service.xml. This makes it really convenient to build, for example, a delete of a Guestbook entity that also deletes entries in the guestbook:

public Guestbook deleteGuestbook(long guestbookId) {
    guestbookEntryLocalService.deleteEntriesByGuestbookId(guestbookId);
    return super.deleteGuestbook(guestbookId);
}

When using the DS injector, these references will not be in the superclass anymore.

If you need the GuestbookEntryLocalService, you have to add a member field to your GuestbookLocalServiceImpl class and @Reference it in.

Circular References

Now you may be thinking, "Well I'll just copy my code that declares the local services and @Reference them to every one of my XxxLocalServiceImpl classes, that way I'll have all of the services the same way I did before."

There's a problem with that thinking, it's the darn circular reference issue. You can have a circular reference if component A @References in component B, and component B @References in component A. Since A depends on B but B isn't available, A cannot start. Since there's no A, B's dependency is also not satisfied and it cannot start.

So basically if GuestbookLocalServiceImpl has an @Reference for a GuestbookEntryLocalService, your GuestbookEntryLocalServiceImpl class cannot @Reference the GuestbookLocalService or you'll end up with a circular reference and neither instance will start.

The tough part with this issue is that it can be fun trying to track down. Your guestbook-service module will easily deploy and start, but anything depending on either of these component services will not. So you'll be tracing it from the thing that is supposed to work but isn't, finding that it doesn't have the service component, only to get back to the component and trying to trace down the chain trying to figure out why things aren't available.

Like I said, fun times.

Resolving Circular References

There are ways to get around the circular dependency issue.

The first is to use ReferenceCardinality.OPTIONAL with your @Reference annotation. With the optional cardinality, OSGi will not require the reference to be satisfied for the component to start. So the GuestbookLocalServiceImpl with the following member field declaration:

@Reference(unbind="-",cardinality=ReferenceCardinality.OPTIONAL)
protected GuestbookEntryLocalService guestbookEntryLocalService;

With this change to the @Reference annotation, the GuestbookLocalService component will be able to start if there is a GuestbookEntryLocalService available or not. So the circle will be broken and, once a GuestbookEntryLocalService instance is available, OSGi will inject it.

There is a catch here, it is possible an instance of GuestbookEntryLocalService is never available, leaving this field as null and possibly causing a NullPointerException when you try to use it. If GuestbookEntryLocalServiceImpl had an @Reference dependency on a service that wasn't deployed, for example, it couldn't start and therefore the GuestbookLocalServiceImpl's dependency wouldn't be resolved.

Another option is not to rely on @Reference at all, instead using a ServiceTracker to track and use available services. You could add code like the following to GuestbookLocalServiceImpl:

public static GuestbookEntryLocalService getService() {
  return _serviceTracker.getService();
}

private static ServiceTracker<GuestbookEntryLocalService, GuestbookEntryLocalService> _serviceTracker;

static {
  Bundle bundle = FrameworkUtil.getBundle(GuestbookEntryLocalService.class);

  ServiceTracker<GuestbookEntryLocalService, GuestbookEntryLocalService> serviceTracker = 
    new ServiceTracker<GuestbookEntryLocalService, GuestbookEntryLocalService>(bundle.getBundleContext(),
      GuestbookEntryLocalService.class, null);

  serviceTracker.open();

  _serviceTracker = serviceTracker;
}

You just invoke getService() method to get a service reference.

Again, however, this too would return null if there is not GuestbookEntryLocalService instance available.

Choosing The Right Dependency Injector

So you might be asking which dependency injector is the best for you?

If you use Spring, you get all of the services for entities in the service.xml file injected and available to use. Even though your services are exposed as OSGi components, you cannot use the @Reference annotation to inject any OSGi component services into your classes. To use OSGi components, you'd need to use a service tracker to get access to them.

If you use DS, you can use @Reference injection in your components, but you lose the automatic injection of services for entities in the service.xml file. You have to avoid the common traps like a missing bnd declaration and circular references.

Like I said at the beginning, consuming the services in your portlet or other code is easy, you just @Reference inject them in, so this aspect shouldn't play a role in the decision.

Me, personally? I kind of prefer the Spring injection. I don't want to have to manage the wiring of the services for entities in the same service.xml, plus I don't mind using service trackers when necessary to access OSGi services.

But I don't think you can go wrong using DS either, as long as you avoid the common traps.

Blogs

Yeah, this doesn't actually work for me. I'm currently using Liferay 7.3 GA6 and when I do something like the following in my xXXPortlet.java and deploy it, the portlet disappears from the page and I can't find it to re-add from the liferay Edit/Fragments and Widgets section. It just disappears. So, I can't even use this portlet. Why would it not appear??

public xXXLocalService getMyLocalService() {

return _myLocalService;

 }

 @Override

 public void render(RenderRequest request, RenderResponse response) throws IOException, PortletException {

 

request.setAttribute("myLocalService", getMyLocalService());

 super.render(request, response);

}

 @Reference

 private xXXLocalService _myLocalService;