How to use OSGi getServiceReference() right

12,986

Solution 1

Looks like you've confused implementation and interface

Using the actual interface for the name (and importing the interface , which you'll end up doing anyway) reenforces the interface contract that services are designed around. You don't care about the implemenation of a LogService but you do care about the interface. Every LogService will need to implement the same interface, hence your use of the interface to get the service. For all you know the LogService is really a wrapper around SLF4J provided by some other bundle. All you see is the interface. That's the loose coupling you're looking for. You don't have to ship the interface with every implementation. Leave the interface it's own bundle and have multiple implementations of that interface.

Side note: ServiceTracker is usually easier to use, give it a try!

Added benefits: Using the interface get the class name avoids spelling mistakes, excessive string literals, and makes refactoring much easier.

After you've gotten the ServiceReference, your next couple lines will likely involve this:

Object logSvc = content.getService(logRef)

// What can you do with logSvc now?!? It's an object, mostly useless

// Cast to the interface ... YES! Now you need to import it!
LogSerivce logger = (LogService)logSvc;

logger.log(LogService.LOG_INFO, "Interfaces are a contract between implementation and consumer/user");

Solution 2

If you use the LogService, you're coupled to it anyway. If you write middleware you likely get the name parameterized through some XML file or via an API. And yes, "LogService" will fail terribly, you need to use the fully qualified name: "org.osgi.service.log.LogService". Main reason to use the LogService.class.getName() pattern is to get correct renaming when you refactor your code and minimize spelling errors. The next OSGi API will very likely have:

ServiceReference<S> getServiceReference(Class<S> type)

calls to increase type safety.

Anyway, I would never use these low level API unless you develop middleware. If you actually depend on a concrete class DS is infinitely simpler, and even more when you use it with the bnd annotations (http://enroute.osgi.org/doc/217-ds.html).

@Component
class Xyz implements SomeService {
  LogService log;

  @Reference
  void setLog( LogService log) { this.log = log; }

  public void foo() { ... someservice ... }

}

If you develop middleware you get the service classes usually without knowing the actual class, via a string or class object. The OSGi API based on strings is used in those cases because it allows us to be more lazy by not creating a class loader until the last moment in time. I think the biggest mistake we made in OSGi 12 years ago is not to include the DS concepts in the core ... :-(

Solution 3

You cannot use value "LogService" as a class name to get ServiceReference, because you have to use fully qualified class name "org.osgi.services.log.LogService".

If you import package this way: org.osgi.services.log;resolution:=optional and you use ServiceTracker to track services in BundleActivator.start() method I suggest to use "org.osgi.services.log.LogService" instead of LogService.class.getName() on ServiceTracker initializazion. In this case you'll not get NoClassDefFoundError/ClassNotFountException on bundle start.

Share:
12,986
Jens
Author by

Jens

Hi 👋, my name is Jens Lauterbach and I am a backend software engineer with a life long passion for writing code and doing DevOps with Go, AWS and Terraform. I have been "at it" for over 20 years and I do not see me stopping any time soon. Professionally, I am spending my days at Netcentric, mostly building serverless applications. In my spare time I am working on a small tool called ddbt and in general try to hone my skills with different side projects.

Updated on June 04, 2022

Comments

  • Jens
    Jens about 2 years

    I am new to OSGi and came across several examples about OSGi services.

    For example:

    import org.osgi.framework.*;
    import org.osgi.service.log.*;
    
    public class MyActivator implements BundleActivator {
      public void start(BundleContext context) throws Exception {
        ServiceReference logRef = 
          context.getServiceReference(LogService.class.getName());
      }
    }
    

    My question is, why do you use

    getServiceReference(LogService.class.getName())
    

    instead of

    getServiceReference("LogService")
    

    If you use LogService.class.getName() you have to import the Interface. This also means that you have to import the package org.osgi.services.log in your MANIFEST.MF.

    Isn't that completely counterproductive if you want to reduce dependencies to push loose coupling? As far as I know one advantage of services is that the service consumer doesn't have to know the service publisher. But if you have to import one specific Interface you clearly have to know who's providing it. By only using a string like "LogService" you would not have to know that the Interface is provided by org.osgi.services.log.LogService.

    What am I missing here?