Control OSGi DS Component Instances
I recently came across some use cases where a more fine grained control is needed for component instance creation. I spent some time in investigating how this is done with OSGi Declarative Services in detail. It turned out that it is easier as it seems, mainly because of missing or misleading tutorials. Therefore I decided to write a new blog post about that topic as part of my OSGi Declarative Service blog post series.
For the start you need to know that by default there is only one component configuration created and activated in the OSGi runtime at the same time. This means that every bundle is sharing the same component instance. So you have a singleton instance for every service.
Note:
Singleton instance in terms of “one single instance” not “Singleton Pattern”!
If you think about multi-threading or context dependent services, you may need multiple instances of a service. In an OSGi environment there are basically the following categories:
- one service instance per runtime
- one service instance per bundle
- one service instance per component/requestor
- one service instance per request
Instance creation control can only be done for service components. So ensure to specify the service annotation type element in @Component
if the implementation does not implement an interface.
To control the instance creation you can use the following mechanisms:
- DS 1.2 -
servicefactory
annotation type element of@Component
- DS 1.3 -
scope
annotation type element of@Component
to specify the service scope - DS 1.2 / DS 1.3 - Create a factory component by using the
factory
annotation type element of@Component
Preparation
For some hands on this topic, we first create some bundles to play with.
Note:
I don’t want to explain every step for creating services in detail in this blog post. If you don’t know how to perform the necessary steps, please refer to my Getting Started with OSGi Declarative Services blog post.
- Create an API bundle
org.fipro.oneshot.api
with a service interfaceOneShot
public interface OneShot { void shoot(String target); }
- Create a provider bundle
org.fipro.oneshot.provider
with a service implementationHitman
@Component public class Hitman implements OneShot { private static AtomicInteger instanceCounter = new AtomicInteger(); private final int instanceNo; public Hitman() { instanceNo = instanceCounter.incrementAndGet(); } @Override public void shoot(String target) { System.out.println("BAM! I am hitman #" + instanceNo + ". And I killed " + target); } }
This implementation will count the number of instances in a static field and remembers it in a member variable, so we can identify the created instance when the service is called.
- Create a command bundle
org.fipro.oneshot.command
with a console command to call the service@Component( property= { "osgi.command.scope=fipro", "osgi.command.function=kill" }, service=KillCommand.class) public class KillCommand { private OneShot killer; @Reference void setOneShot(OneShot oneShot) { this.killer = oneShot; } public void kill(String target) { killer.shoot(target); } }
- Create a command bundle
org.fipro.oneshot.assassinate
with two different console commands that call the service@Component( property= { "osgi.command.scope=fipro", "osgi.command.function=assassinate" }, service=AssassinateCommand.class ) public class AssassinateCommand { private OneShot hitman; @Reference void setOneShot(OneShot oneShot) { this.hitman = oneShot; } public void assassinate(String target) { hitman.shoot(target); } }
@Component( property= { "osgi.command.scope=fipro", "osgi.command.function=eliminate" }, service=EliminateCommand.class ) public class EliminateCommand { private ComponentContext context; private ServiceReference<OneShot> sr; @Activate void activate(ComponentContext context) { this.context = context; } @Reference(name="hitman") void setOneShotReference(ServiceReference<OneShot> sr) { this.sr = sr; } public void eliminate(String target) { OneShot hitman = (OneShot) this.context.locateService("hitman", sr); hitman.shoot(target); } }
The EliminateCommand
uses the Lookup Strategy to lazily activate the referenced component.
In this example probably quite useless, but I wanted to show that this also works fine.
Note:
I am using the DS 1.2 notation here to make it easier to follow the example in both worlds. In the DS 1.3 only examples later in this blog post, you will see the modified version of the components using DS 1.3 annotations.
The sources for this blog post can be found on GitHub:
One instance per runtime
There is not much to say about this. This is the default behavior if you do not specify something else. There is only one component configuration created and activated. Therefore only one component instance is created and shared between all bundles.
In DS 1.2 a singleton instance can be explicitly configured on the component like this:
@Component(servicefactory=false)
public class Hitman implements OneShot {
In DS 1.3 a singleton instance can be explicitly configured on the component like this:
@Component(scope=ServiceScope.SINGLETON)
public class Hitman implements OneShot {
Note:
For Immediate Components and Factory Components it is not allowed to use other values for servicefactory
or scope
!
If you launch an OSGi application with the necessary bundles (org.apache.felix.scr
, org.apache.felix.gogo.*
, org.fipro.oneshot.*
) and call the commands one after the other, you should get an output similar to this (on a Felix console):
g! kill Dirk
BAM! I am hitman #1. And I killed Dirk
g! assassinate Dirk
BAM! I am hitman #1. And I killed Dirk
g! eliminate Dirk
BAM! I am hitman #1. And I killed Dirk
Every command has a reference to the same Hitman
instance, as can be seen by the instance counter in the output.
One instance per bundle
There are use cases where it is useful to have one component configuration created and activated per bundle. For example if the component configuration contains special bundle related configurations.
In DS 1.2 a bundle scope service can be configured on the component like this:
@Component(servicefactory=true)
public class Hitman implements OneShot {
In DS 1.3 a bundle scope service can be configured on the component like this:
@Component(scope=ServiceScope.BUNDLE)
public class Hitman implements OneShot {
When launching the OSGi application and calling the commands one after the other, you should get an output similar to this (on a Felix console):
g! kill Dirk
BAM! I am hitman #1. And I killed Dirk
g! assassinate Dirk
BAM! I am hitman #2. And I killed Dirk
g! eliminate Dirk
BAM! I am hitman #2. And I killed Dirk
g! kill Dirk BAM! I am hitman #1. And I killed Dirk
You can see that the kill command has a reference to the Hitman
instance #1, while the assassinate and the eliminate command both have a reference to the Hitman
instance #2, as both reside in the same bundle.
One instance per requestor
There are some use cases where every consumer needs its own instance of a service. With DS 1.2 you could achieve this by creating a Factory Component. As this is basically the same as getting a service instance per request, I will explain the Factory Component in the following chapter. For now I will focus on the DS 1.3 variant to create and use a service instance per requestor.
In DS 1.3 the PROTOTYPE scope was introduced for this scenario.
@Component(scope=ServiceScope.PROTOTYPE)
public class Hitman implements OneShot {
Setting the scope of the service component to PROTOTYPE does not mean that every consumer gets a distinct service instance automatically. By default the result will be the same as with using the BUNDLE scope. So if you start the application with the updated Hitman
service, you will get the same result as before.
The reason for this is the reference scope that was also introduced with DS 1.3. It is configured on the consumer side via @Reference
and specifies how the service reference should be resolved. There are three possible values:
- BUNDLE
All component instances in a bundle will use the same service object. (default) - PROTOTYPE
Every component instance in a bundle may use a distinct service object. - PROTOTYPE_REQUIRED
Every component instance in a bundle must use a distinct service object.
As the default of the reference scope is BUNDLE, we see the same behavior for service scope PROTOTYPE as we saw for service scope BUNDLE. That means the consumer components need to be modified to achieve that every one gets its own service instance.
@Component(
property= {
"osgi.command.scope=fipro",
"osgi.command.function=assassinate"
},
service=AssassinateCommand.class )
public class AssassinateCommand {
@Reference(scope=ReferenceScope.PROTOTYPE_REQUIRED)
private OneShot hitman;
public void assassinate(String target) {
hitman.shoot(target);
}
}
@Component(
property= {
"osgi.command.scope=fipro",
"osgi.command.function=eliminate"
},
service=EliminateCommand.class,
reference=@Reference(
name="hitman",
service=OneShot.class,
scope=ReferenceScope.PROTOTYPE_REQUIRED)
)
public class EliminateCommand {
private ComponentContext context;
@Activate
void activate(ComponentContext context) {
this.context = context;
}
public void eliminate(String target) {
OneShot hitman = (OneShot) this.context.locateService("hitman");
hitman.shoot(target);
}
}
Note:
The above examples are showing the DS 1.3 version of the command services. You should recognize the usage of the field strategy and the DS 1.3 lookup strategy, which makes the code more compact.
Note:
In the example I have chosen to use the reference scope PROTOTYPE_REQUIRED. In the given scenario also PROTOTYPE would be sufficient, as the concrete service implementation uses the PROTOTYPE service scope. But IMHO it is better to specify directly which reference scope to use, instead of having a weak rule.
When launching the OSGi application and calling the commands one after the other, you should get an output similar to this (on a Felix console):
g! kill Dirk
BAM! I am hitman #1. And I killed Dirk
g! assassinate Dirk
BAM! I am hitman #2. And I killed Dirk
g! eliminate Dirk
BAM! I am hitman #3. And I killed Dirk
g! kill Dirk
BAM! I am hitman #1. And I killed Dirk
You can see that every command gets its own service instance.
One instance per request
In some use cases it is required to have a distinct service instance per request. This is for example needed for web requests, where it is required that services are created and destroyed in every request, or for multi-threading where services can be executed in parallel (hopefully without side-effects).
With DS 1.2 a Factory Component needs to be used. With DS 1.3 again the PROTOTYPE scope helps in solving that requirement. In both cases some OSGi DS API needs to be used to create (and destroy) the service instances.
First lets have a look at the DS 1.3 approach using PROTOTYPE scoped services and the newly introduced ComponentServiceObjects
interface. The implementation of the ComponentServiceObjects
is a factory that allows to create and destroy service instances on demand. The following example shows the usage. Create it in the org.fipro.oneshot.command
bundle.
@Component(
property= {
"osgi.command.scope=fipro",
"osgi.command.function=terminate"
},
service=TerminateCommand.class )
public class TerminateCommand {
// get a factory for creating prototype scoped service instances
@Reference(scope=ReferenceScope.PROTOTYPE_REQUIRED)
private ComponentServiceObjects<OneShot> oneShotFactory;
public void terminate(String target) {
// create a new service instance OneShot
oneShot = oneShotFactory.getService();
try {
oneShot.shoot(target);
}
finally {
// destroy the service instance
oneShotFactory.ungetService(oneShot);
}
}
}
Note:
There is no special modification needed in the component configuration of the provider. It simply needs to be configured with a PROTOTYPE service scope as shown before. The consumer needs to decide what instance should be referenced, the same as every service in the bundle, a new one for every component or a new one for each request.
Executing the terminate command multiple times will show that for each call a new Hitman
instance is created. Mixing it with the previous commands will show that the other services keep a fixed instance, while terminate constantly will create and use a new instance per execution.
g! kill Dirk
BAM! I am hitman #1. And I killed Dirk
g! terminate Dirk
BAM! I am hitman #2. And I killed Dirk
g! terminate Dirk
BAM! I am hitman #3. And I killed Dirk
g! terminate Dirk
BAM! I am hitman #4. And I killed Dirk
g! kill Dirk
BAM! I am hitman #1. And I killed Dirk
Factory Component
With DS 1.2 you need to create a Factory Component to create a service instance per consumer or per request. The Factory Component is the third type of components specified by the OSGi Compendium Specification, next to the Immediate Component and the Delayed Component. It therefore also has its own lifecycle, which can be seen in the following diagram.
When the component configuration is satisfied, a ComponentFactory
is registered. This can be used to activate a new component instance, which is destroyed once it is disposed or the component configuration is not satisfied anymore.
While this looks quite complicated on first sight, it is a lot easier when using DS annotations. You only need to specify the factory
annotation type element on @Component
. The following snippet shows this for a new OneShot
implementation. For the exercise add it to the org.fipro.oneshot.provider
bundle.
@Component(factory="fipro.oneshot.factory")
public class Shooter implements OneShot {
private static AtomicInteger instanceCounter = new AtomicInteger();
private final int instanceNo;
public Shooter() {
instanceNo = instanceCounter.incrementAndGet();
}
@Override public void shoot(String target) {
System.out.println("PEW PEW! I am shooter #" + instanceNo + ". And I hit " + target);
}
}
As explained above, the SCR will register a ComponentFactory
that can be used to create and activate new component configurations on demand. On the consumer side this means it is not possible to get a Shooter
service instance via @Reference
, as it is not registered as a Delayed Component. You need to reference a ComponentFactory
instance by specifying the correct target property. The target property needs to be specified for the key component.factory
and the value of the factory
annotation type element on the @Component
annotation of the Factory Component.
The following snippet shows the consumer of a Factory Component. Create it in the org.fipro.oneshot.command
bundle.
@Component(
property= {
"osgi.command.scope=fipro",
"osgi.command.function=shoot"
},
service=ShootCommand.class )
public class ShootCommand {
@Reference(target = "(component.factory=fipro.oneshot.factory)")
private ComponentFactory factory;
public void shoot(String target) {
// create a new service instance
ComponentInstance instance = this.factory.newInstance(null);
OneShot shooter = (OneShot) instance.getInstance();
try {
shooter.shoot(target);
}
finally {
// destroy the service instance
instance.dispose();
}
}
}
Comparing the Factory Component with the PROTOTYPE scoped service, the following differences can be seen:
- A PROTOTYPE scoped service is a Delayed Component, while the Factory Component is a different component type with its own lifecycle.
- A Factory Component can only be consumed by getting the
ComponentFactory
injected, while a PROTOTYPE scoped service can be created and consumed in different ways. - A component configuration needs to be provided when creating the component instance via
ComponentFactory
. A PROTOTYPE scoped service can simply use the configuration mechanisms provided in combination with the Configuration Admin. ComponentServiceObjects
is type-safe. The result ofComponentInstance#getInstance()
needs to be cast to the desired type.
Compared to creating the service instance by using the constructor, the nice thing on using a Factory Component or a PROTOTPYE scoped service is that the configured service references are resolved by the SCR. You could verify this for example by adding a reference to the StringInverter
service from my previous blog post.
Note:
To create an instance per requestor by using a Factory Component, you would simply create the instance in the @Activate
method, and dispose it on @Deactivate
.
Component Instance cleanup
When Peter Kirschner (Twitter: @peterkir) and I prepared our tutorial for the EclipseCon Europe 2016, we noticed a runtime difference between Equinox DS and Felix SCR. In the Console Exercise we also talked about the lifecycle methods and wanted to show them. So we added the @Activate
and the @Deactivate
method to the StringInverterImpl and the StringInverterCommand
. Running the example on Equinox and executing the console command showed a console output for activating the service and the command. But both never were deactivated. Running the example with Felix SCR the StringInverterCommand
was activated, executed and deactivated right after the execution. We wondered about that different behavior, but were busy with other topics, so we didn’t search further for the cause.
Note:
The tutorial sources and slides can be found on GitHub.
I recently learned what causes this different behavior and how it can be adjusted.
For Delayed Components the OSGi Compendium Specification says: If the service registered by a component configuration becomes unused because there are no more bundles using it, then SCR should deactivate that component configuration.
Should is a quite weak statement, so it is easy to have a different understanding of this part of the specification. Apache Felix SCR is taking that statement very serious and deactivates and destroys the component once the last consumer that references the component instance is done with it. Equinox DS on the other hand keeps the instance. At least this is the default behavior in those SCR implementations. But both can be configured via system properties to behave differently.
To configure Equinox DS to dispose component instances that are no longer used (like the Felix SCR default behavior), use the following JVM parameter (see Equinox DS Runtime Options):
-Dequinox.scr.dontDisposeInstances=false
To configure Felix SCR to keep component instances and not dispose them once they are no longer used (like the Equinox DS default behavior), use the following Framework property, e.g. by setting it as JVM parameter (see Felix SCR Configuration):
-Dds.delayed.keepInstances=true
To get some more insight on this you might also want to look at the ticket in the Felix bug tracker where this was discussed.
To experiment with that you can modify for example Hitman
and KillCommand
and add methods for @Activate
and @Deactivate
.
@Activate
void activate() {
System.out.println(getClass().getSimpleName() + " activated");
}
@Deactivate
void deactivate() {
System.out.println(getClass().getSimpleName() + " deactivated");
}
Add the JVM arguments for the runtime you are experimenting with and check the results.