Dynamic Service Layer
The Dynamic Service Layer is the core communication infrastructure of the OSGi runtime. This layer provides an in-memory service registry on which service provider bundles can register/unregister service objects. Service consumer bundles can find/bind these services to use the provided functionality. In OSGi terms, a service is a plain object which provides methods and/or attributes which can be used by client bundles. Consider an illustrative example:
The core system bundle acts as a service provider bundle and registers some services, in this case a LogService and an EventService. The locateme bundle acts as a service consumer bundle and searches for an EventService and a LogService and uses them to log some messages and to broadcast a new geolocation. Both bundles are using the service registry provided by the OSGi runtime to establish communication between each other. It is important to understand that the Service Registry pattern has the following main advantages:
-
Minimizing lifetime dependencies (start-up order is not important)
-
No direct dependencies between bundles (locateme only assumes that there is an EventService, but does not know that it is provided by the system bundle)
-
Fully dynamic: Services can be registered and unregistered in the system at any time
-
Defining a clean way how a bundle can provide new functionality to the system (register a service)
Register a service
To register a service in the JS OSGi runtime, the method registerService
of the BundleContext
is used.
A service is registered using well-defined interface names, service properties, and the service instance.
The service properties are a very important feature, because they are used to provide metadata about a service instance.
A service interface specification might require that a service implementation must provide well-defined service properties next to the interface name.
This means that service properties are part of the service interface and therefore should be defined with care.
import MyService from "./MyService";
export default class {
start(bundleContext){
// create the service instance
let serviceInstance = new MyService();
// define service metadata properties, usable to filter service instances
let serviceProperties = {
// because of some speed tests
// now this service reacts on function calls in ~100ms
"averageSpeedInMilliSeconds": 100
};
// the interface names provided by the service
// you can also consider this as the names under which the service can be found in the registry
let interfaces = ["interfacenamespace.MyServiceInterface"];
this._serviceregistration = bundleContext.registerService(interfaces, serviceInstance, serviceProperties);
}
}
The method BundleContext.registerService
returns an apprt/ServiceRegistration
instance that can be used to unregister the service and to change the service properties during runtime.
A ServiceRegistration instance should be considered as bundle-private.
This means that you should never give access to such instances to enother bundle.
A bundle is fully responsible for correct clean up of service registrations.
Unregister a service
Use the unregister
method of a ServiceRegistration instance to unregister a service.
export default class {
...
stop(bundleContext){
// get ServiceRegistration object (stored during service registration)
let registration = this._serviceregistration;
// clear the reference
this._serviceregistration = null;
// call unregister
registration.unregister();
}
}
Change the service properties
If a provider bundle needs to change the service properties, it can use the ServiceRegistration.setProperties
method.
It is not recommended to overuse this method, because the service properties are part of the interface definition.
Therefore each consumer bundle must check if it is still correct to use a once bound service, which can result in long rebind/restart flows.
export default class {
...
amethod: function(){
// get ServiceRegistration object (stored during service registration)
let registration = this._serviceregistration;
// update properties
registration.setProperties({
// some statistic calculations say here is a slow connection
"averageSpeedInMilliSeconds": 1000
});
}
...
}
Find a service
A consumer bundle can search the service registry to find specific services by using the method getServiceReferences
of the BundleContext.
The search result is highly dynamic because a service can be removed or added at any time.
export default class {
...
start(bundleContext){
let matchingServiceReferences = bundleContext.getServiceReferences("interfacenamespace.MyServiceInterface");
matchingServiceReferences.forEach((serviceReference)=>{
console.debug("find service: " + serviceReference);
});
}
}
The getServiceReferences
method also allows to use filter expressions to filter services by their properties.
To get more details about filter expressions, read Search for bundles.
export default class {
start(bundleContext){
// look for MyServiceInterface providers, which react faster than 200 milliseconds
var matchingServiceReferences = bundleContext.getServiceReferences("interfacenamespace.MyServiceInterface", "(averageSpeedInMilliSeconds<200)");
}
}
The service references returned by the getServiceReferences
method are ordered after following conditions:
-
Service ranking
-
Insertion order
Service-Ranking
is a special service property that can be set during the registration of a service.
The default value is 0
.
The service with the highest ranking number is on the head of the returned list.
The service with the lowest ranking is on the end of the list.
Services which have equal ranking numbers are sorted by their insertion time.
Services inserted first are on the head of the list.
Access service properties
The ServiceReference
objects returned by the getServiceReferences
method can be used to obtain service metadata.
With the method ServiceReference.getProperty
any metadata value can be retrieved.
The method ServiceReference.getProperties
returns a plain object which can be used to read the properties.
The method ServiceReference.getPropertyKeys
returns an array with the property names.
If you want to know the origin of a service you can use the method ServiceReference.getBundle
.
export default class {
start(bundleContext){
let matchingServiceReferences = bundleContext.getServiceReferences("interfacenamespace.MyServiceInterface");
matchingServiceReferences.forEach((serviceReference)=>{
// access single property
let speed = serviceReference.getProperty("averageSpeedInMilliSeconds");
console.debug("find service: " + serviceReference + " it is very fast around: " + speed);
// get property keys as array
let keys = serviceReference.getPropertyKeys();
// get all properties as object
let props = serviceReference.getProperties();
keys.forEach((key)=>{
console.debug("key: " +key + " value: " + props[key]);
});
// get the bundle providing the service
let serviceProviderBundle = serviceReference.getBundle();
...
});
}
}
Bind/Unbind a service
A ServiceReference
can be used to get the real service instance by using the BundleContext.getService
method.
If a client no longer needs a service, it has to release the service instance using the BundleContext.ungetService
method.
export default class {
start(bundleContext){
// find possible service references
let matchingServiceReferences = bundleContext.getServiceReferences("interfacenamespace.MyServiceInterface");
// get first service reference
let serviceReference = matchingServiceReferences[0];
try{
// get service instance
let service = bundleContext.getService(serviceReference);
// do something with the service
...
}finally{
// unget the service if it is no longer used
bundleContext.ungetService(serviceReference);
}
}
}
The client is responsible for the correct resource management of the used services.
This means that a client bundle must make sure that it releases a bound service instance.
Because a service can be unregistered at any time, it can happen that a bound service is unregistered and not functional.
Therefore, a client bundle has to register a ServiceListener
to react correctly in such dynamic situations.
Listen to service events
The following ServiceEvents are fired by the OSGi Runtime and are also transported via the EventService, so the topics are listed, too.
Type | Topic | Description |
---|---|---|
|
|
Fired when a new service is registered. |
|
|
Fired when a service should be unregistered (consumers should |
|
|
Fired when the service properties of a service are changed. |
The BundleContext
can be used to listen to ServiceEvents using the addServiceListener
method.
import ServiceEvent from "apprt/ServiceEvent";
export default class {
start(bundleContext){
// register event listener function
// without a filter it listens for any service event in the runtime
this._serviceEventListenerHandle = bundleContext.addServiceListener(null,this._onServiceEvent,this);
},
stop : function(bundleContext){
// unregister event listener
bundleContext.removeServiceListener(this._serviceEventListenerHandle);
this._serviceEventListenerHandle= null;
},
_onServiceEvent: function(evt){
var types = ServiceEvent.types;
var serviceReference = evt.getServiceReference();
switch (evt.getType()){
case types.REGISTERED:
...
break;
case types.UNREGISTERING:
...
break;
case types.MODIFIED:
...
break;
}
}
}
The addServiceListener method accepts a filter expression, which allows to register service listeners for specific events. If you wish to listen only for events of services providing a special interface, you have to use the property 'objectClass' in the filter expression. To get more details about filter expressions, see Search for Bundles.
export default class {
start(bundleContext){
// registers only for events of services with interface 'interfacenamespace.MyServiceInterface'
this._serviceEventListenerHandle = bundleContext.addServiceListener("(objectClass=interfacenamespace.MyServiceInterface)",this._onServiceEvent,this);
}
_onServiceEvent(evt){
...
}
}
Register a ServiceFactory
Next to the normal service registration, there exists a special service registration pattern, which allows the creation of services dependent on the using bundles.
Instead of directly registering the service instance, a service factory is registered.
A service factory must provide the methods getService
and ungetService
.
If a new bundle is using the registered service, getService
is called by the JS OSGi runtime on the service factory.
The created service instance is cached for each bundle by the JS OSGi runtime and reused if a bundle is using the service again.
If a bundle is no longer using a service, the JS OSGi runtime is calling ungetService
on the service factory to release the service instance.
import MyService from "./MyService";
export default class {
start(bundleContext){
...
// sample of registration of an inlined service factory
this._serviceregistration = bundleContext.registerService(["my.Interface"], {
getService(usingBundle,serviceregistration){
// do something with the bundle information...
// a MyService is created for each using bundle
return new MyService();
},
ungetService(usingBundle,serviceregistration,instance){
// do something with the bundle information...
// clean up the instance resources
}
}
}
}