This project contains a couple of samples for usages of different OSGi concepts and technologies, most of them in relation to consuming services provided by another bundle. It is intended to be used as a breakable toy in order to understand the magic OSGi uses for providing its functionality.
Currently there are examples for the following classes/concepts:
ServiceTracker
ServiceListener
ServiceTrackerCustomizer
(in combination with theServiceTracker
)- Blueprint
To be able to see the stuff in action, there are two services that can be consumed:
- A
RandomNumberGenerator
that generates a random integer value - A
RandomStringGenerator
that generates a string containing a random Gaussian value
Each of the two services consists of two bundles, one with the API that will be consumed by others and another one containing the actual service implementation (which should be hidden from the consumers)
The bundle structure is represented in the Gradle structure of the project: Each bundle gets its own module.
To simplify the addition of new bundles (in particular the generation of a build.gradle
file), there's the osgi.gradle
file that includes the basic things needed in order to make a Gradle module being deployed to the OSGi container.
The main benefit of the file is that the build.gradle
file of a bundle will look like this (assuming there is nothing like additional dependencies):
group 'de.l7r7.proto'
version '0.1'
apply from: "$rootDir/gradle/osgi.gradle"
This is not the most elegant solution and should probably not be used directly in any production project, but it allows the fast creation of new bundles without much overhead.
- consumer-bean-initialization: This example combines the usage of blueprint to get two service instances (namely a
RandomNumberGenerator
and aRandomStringGenerator
) and as an IoC container to provide objects for a field. The object provided to theMain
class is theStringConcatenator
which itself needs aStringToLower
object.StringToLower
in turn needs aStringProvider
(Yes, the things these three classes do are pretty stupid but they serve the purpose of showing how to use blueprint for dependency injection). - consumer-blueprint-number: A
RandomNumberGenerator
service is provided via blueprint - consumer-blueprint-string: A
RandomStringGenerator
service is provided via blueprint - consumer-listener: The bundle's Activator will register a
ServiceListener
to get aRandomNumberGenerator
service instance. The problem with this approach is, that it won't get a service that is present before the bundle itself is started. - consumer-listener-tracker: This bundle combines a
ServiceTracker
and aServiceListener
to get aRandomNumberGenerator
service instance. Basically, this bundle combines the functionality of the consumer-tracker bundle with the consumer-listener bundle. - consumer-multi-service: not implemented yet
- consumer-pretty-listening-tracker: This bundle does the same thing as the consumer-listener-tracker bundle encapsulates the OSGi complexity in a utility class called
CustomGenericDefaultServiceObservingProvidility
. (Don't take this too serious 😉) - consumer-tracker: The bundle's Activator will use a
ServiceTracker
to get aRandomNumberGenerator
service instance. The caveat here is that the tracker isn't capable of handling services that appear and disappear at runtime with this implementation. - consumer-tracker-customizer: This bundle provides a
RandomNumberGenerator
service instance by using aServiceTracker
with aServiceTrackerCustomizer
. With the Customizer it is possible to handle services that come and go at runtime. TheServiceTrackerCustomizer
seems to be the intended way to handle dynamic service instances properly (besides Blueprint) - consumer-tracker-customizer-filter: This bundle makes use of the same basic principle as the consumer-tracker-customizer but the
ServiceTracker
is tracking both theRandomNumberGenerator
and theRandomStringGenerator
by specifying aFilter
for theServiceTracker
. The services are only consumed if both of them are available (this assumption adds some more complexity and it is done on purpose to simulate a real use case). Since the two services are of different types, the tracker (and the customizer of course) have to be more generic to be able to deal with the different types. In the example, the common type of the two services isObject
. This requires ainstanceof
check every time a service appears or disappears. Apart from that a further check is necessary whenever aServiceReference
is used: AServiceReference
is generic and wraps a service instance of a certain type. Due to the nature of the JVM, the type of a Generic object can't be determined at runtime. To get the type (more specifically the class name) of the referenced service, theServiceReference
has a property that can be accessed like this:String objectClass = ((String[]) reference.getProperty(Constants.OBJECTCLASS))[0];
1 An alternative approach for the problem of having to track multiple services is to use an individualServiceTracker
for each service. However, since there has to be aServiceTrackerCustomizer
for each tracker, the code becomes confusing pretty quickly. This approach becomes much more complicated when the number of services increases. As a rule of thumb: If there are more than two services (and especially if all of them are required), use a Filter. Otherwise use individual trackers. - consumer-util: This bundle contains the
CustomGenericDefaultServiceObservingProvidility
class used by the consumer-pretty-listening-tracker bundle. - service-number-api: This bundle contains the interface for the
RandomNumberGenerator
. The service interfaces are separated from their implementation to keep them stable and to hide implementation details. - service-number-impl: This bundle contains the implementation of the
RandomNumberGenerator
interface. The wiring is done using blueprint. - service-string-api: This bundle contains the interface for the
RandomStringGenerator
. - service-string-impl: This bundle contains the implementation of the
RandomStringGenerator
interface. As in the service-number-impl bundle, the implementation is bound to the interface by using blueprint. - servlet-filter: Here you can find an approach on registering a servlet filter to a servlet.
- Blueprint will provide service implementations after the bundle has started. The order in which services will appear is non-deterministic and it could be that the bundle has to run for a while before all the services are present (even if the services are present when the bundle is started). After making a application ready for this, things like null-checks will be all over the place (null-checks are never a bad idea in OSGi anyways).
- In contrast to the blueprint approach, with the
ServiceTracker
it is possible to get the (existing) services before the bundle reaches its "started" state. However, if you want to be safe against dynamic services, you have to add aServiceTrackerCustomizer
which roughly adds the functionality of aServiceListener
to the tracker. (An alternative is the Providility class 😉) - To get the type of the service a
ServiceReference
is referring to, you can get the property with the key "objectClass" (or even better: useConstants.OBJECTCLASS
from theorg.osgi.framework
package). This will return an array(!) of strings containing the class names of the referenced service.
If you want to play around with the examples, feel free to fork the project. Pull requests are highly appreciated as well. If there are any questions or problems, open an issue or ping me on Twitter
1 At the moment I'm not sure if there is a case where the array of objectClasses contains more than one element. ↩