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:
ServiceTrackerServiceListenerServiceTrackerCustomizer(in combination with theServiceTracker)- Blueprint
To be able to see the stuff in action, there are two services that can be consumed:
- A
RandomNumberGeneratorthat generates a random integer value - A
RandomStringGeneratorthat 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
RandomNumberGeneratorand aRandomStringGenerator) and as an IoC container to provide objects for a field. The object provided to theMainclass is theStringConcatenatorwhich itself needs aStringToLowerobject.StringToLowerin 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
RandomNumberGeneratorservice is provided via blueprint - consumer-blueprint-string: A
RandomStringGeneratorservice is provided via blueprint - consumer-listener: The bundle's Activator will register a
ServiceListenerto get aRandomNumberGeneratorservice 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
ServiceTrackerand aServiceListenerto get aRandomNumberGeneratorservice 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
ServiceTrackerto get aRandomNumberGeneratorservice 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
RandomNumberGeneratorservice instance by using aServiceTrackerwith aServiceTrackerCustomizer. With the Customizer it is possible to handle services that come and go at runtime. TheServiceTrackerCustomizerseems 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
ServiceTrackeris tracking both theRandomNumberGeneratorand theRandomStringGeneratorby specifying aFilterfor 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 ainstanceofcheck every time a service appears or disappears. Apart from that a further check is necessary whenever aServiceReferenceis used: AServiceReferenceis 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, theServiceReferencehas 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 individualServiceTrackerfor each service. However, since there has to be aServiceTrackerCustomizerfor 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
CustomGenericDefaultServiceObservingProvidilityclass 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
RandomNumberGeneratorinterface. 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
RandomStringGeneratorinterface. 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
ServiceTrackerit 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 aServiceTrackerCustomizerwhich roughly adds the functionality of aServiceListenerto the tracker. (An alternative is the Providility class 😉) - To get the type of the service a
ServiceReferenceis referring to, you can get the property with the key "objectClass" (or even better: useConstants.OBJECTCLASSfrom theorg.osgi.frameworkpackage). 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. ↩