The OSGi specification defines four different layers:
The Services layer allows bundles to communicate by defining an API that can cross bundle layers. However, the services layer also allows the services to come and go dynamically, instead of being fixed at runtime.
This mechanism allows services to be exported over a network, and since the network can come and go (as can the remote endpoint) the OSGi services layer can replicate that same functionality.
Responding to services dynamically coming and going may add a slight difficulty to the client code, but it will be more robust in case of failure. The following sections will present different ways of achieving dynamism in services.
The easiest way of working with dynamic services is to list the services each time they are needed. The example so far uses this technique to allow different services to be contributed.
This technique can work if the list of services is infrequently needed. However each time the lookup is performed, there is a cost to the acquisition, which may not be desirable.
The OSGi framework provides a ServiceTracker
class which can be used to simplify the acquisition of one or more services in a standard way. Provided in the org.osgi.util.tracker
package, the ServiceTracker
class has a constructor that takes a class and a BundleContext
object, along with an optional filter specification.
Add the package to the timezones
bundle's manifest as an import:
Import-Package: org.osgi.util.tracker
Modify the TimeZonesFactory
so that a ServiceTracker
is acquired in a static
initializer, and that open
is called. This simplifies the use
method to simply delegate to the service tracker:
public class TimeZonesFactory { private static final Bundle bundle = FrameworkUtil.getBundle(TimeZonesService.class); private static final BundleContext context = bundle.getBundleContext(); private static final ServiceTracker<TimeZonesService, TimeZonesService> tracker = new ServiceTracker<>(context,TimeZonesService.class, null); static { tracker.open(); // Remember to call this! } public static void use(Consumer<TimeZonesService> consumer) { consumer.accept(tracker.getService()); } }
The ServiceTracker
also has a close
method, which should be called when services are no longer required to be tracked.
The service tracker, as it is currently implemented, returns all compatible services that implement the interface (if true
is passed to the open
call, both compatible and incompatible services are returned; this should generally not be used).
It is also possible to use a filter to restrict the list of services that are returned. OSGi filters are specified using the LDAP filter syntax, which uses prefix notation and parentheses to group elements. Here is how to read it:
(&(A)(B))
(|(A)(B))
(!(A))
(A=B)
(A=*B*)
These can be nested to form complex queries.
The services
command in the Equinox console allows a filter to be evaluated. Each service is published into the registry, and the filter objectClass=
allows services matching a particular interface to be found, as was done earlier in the chapter:
osgi> services "(objectClass=*.TimeZonesService)" {com.packtpub.e4.timezones.TimeZonesService}={service.ranking=2, component.name=TimeZonesProvider, component.id=0, service.id=48}
It's possible to filter on other properties as well. For example, DS registers a component.id
property with a service, so this can be used to create a filter for just DS registered components:
osgi> services "(&(objectClass=*.TimeZonesService)(component.id=*))" {com.packtpub.e4.timezones.TimeZonesService}={service.ranking=2, component.name=TimeZonesProvider, component.id=0, service.id=48}
This looks for services ending in TimeZonesService
and which have a value for the component.id
property.
Filters can be included in the ServiceTracker
to ensure that only desired services are picked up. For example, to include only services that aren't registered by DS, the following can be coded into the ServiceTracker
:
Filter filter = context.createFilter( "(&(objectClass=*.TimeZonesService)(!(component.id=*)))"); st = new ServiceTracker<TimeZonesService, TimeZonesService>( context, filter, null); st.open();
Since the ServiceTracker
needs the BundleContext
to register a listener, it is conventional to set up a BundleActivator
for the sole purpose of acquiring a BundleContext
.
Since this incurs a performance penalty, using a different mechanism to acquire the context will speed the start-up process. Fortunately there is a class, FrameworkUtil
, that can be used to acquire a Bundle
for any given class, and from there, the BundleContext
. This allows the implementation of the TimeZonesActivator
to be removed:
BundleContext context = FrameworkUtil. getBundle(TimeZonesFactory.class).getBundleContext();
Using this mechanism adds no performance penalty and should be used in favor of a global static instance to the BundleContext
. It also potentially allows the bundle's activator to be removed from the bundle.
It is fairly common that an OSGi service depends on other OSGi services. As such it can help if the services are set up and made available when the bundles are available. The Declarative Services approach can be used to register services on demand when the requirements are satisfied.
For DS, when the cardinality of the relationship is not optional (in other words, the relationship is 1..1
or 1..n
), then the service won't be started until the required dependent services are available. For example, a menu service may not be required until the graphical user interface service is present; and services that wish to contribute to the menu service won't be able to work until the menu service is present.
Delaying the creation of the services until they are needed will result in shorter start-up times of the application.
18.188.108.54