A Ten Minute Introduction to OSGi
Component-based systems have long been a dream and goal for software, ie where an application is created by combining existing open-source and/or commercial components with some application-specific components and a bit of glue logic to solve a real-world problem. Various attempts have come and go, from CORBA to Microsoft OLE. The OSGi approach comes surprisingly near to fulfilling the goal - if you’re working in Java. This is particularly surprising given its relative simplicity and its roots in embedded systems.
OSGi is a specification that describes how to build a large Java application from very loosely-coupled components, and several competing implementations of the specification exist to choose from.
The OSGi alliance have reasonable descriptions of their goals and high-level design here:
The above pages are highly recommended as an initial guide. However what they lack is concrete details. This page gives a quick overview and its companion pages delve somewhat deeper into the underlying implementation details and advantages/drawbacks of OSGi features.
As you may have seen from other articles on this site, my opinion of frameworks and libraries isn’t always positive. However OSGi really does have many very good features that make it worth considering for any significant-sized project.
OSGi falls roughly into the following parts:
- the OSGi classloading model (provides isolation between jarfiles)
- the OSGi service model (provides a registry for singleton objects)
- the OSGi permissions model (enhances the Java SecurityManager for OSGi environments)
- the OSGi configuration system (JNDI-like functionality)
- the OSGi event broker (provides publish/subscribe services)
- a large number of smaller but still useful services gathered into the “OSGi Compendium” (also known as “OSGi Enterprise”).
The most remarkable thing about OSGi is its small size - the core api is just a dozen or so classes, and even the full implementation is a few hundred kilobytes - far smaller and simpler than the J2EE specification for example.
The smallest unit of deployable code in OSGi is the “bundle”, which is effectively a jarfile with some custom entries in the
OSGi bundles have the following mandatory properties (ie entries in MANIFEST.MF):
- Bundle-SymbolicName -> normally this is the FQDN (fully-qualified domain name) of the “primary” Java package in the jarfile
- Bundle-Version -> major.minor.patch version
OSGi bundles have the following important optional properties:
- Bundle-Activator: name of a class to be notified when bundle is “started”/”stopped” (must implement org.osgi.framework.BundleActivator)
- Import-Package: list of (java-package-name, version-range) that classes in this jarfile expect other bundles to provide.
- Export-Package: list of Java packages within this jarfile that external code is allowed to access.
Multiple installed bundles can export the same Java package with different versions; having two bundles export the same package with the same version is probably an error.
The jarfiles containing OSGi bundles must not be on the normal Java classpath; instead they are loaded by creating an OSGi org.osgi.framework.launch.Framework object, and using Framework.getBundleContext().installBundle(path) to load bundle jarfiles. Each of the “installed” bundles is loaded via a separate instance of an OSGi-specific subclass of
Traditional Java classloaders only have parent and child classloaders (never “peers”), and expose all the classes they know about to their child classloaders. OSGi classloaders are instead gathered into what are effectively “peer groups”, but only expose the classes that their jarfile wants to expose, and use only classes from peers which expose classes their jarfile wants in the version that their jarfile wants. The result is significantly reduced coupling between jarfiles compared to normal Java applications, and the ability for multiple versions of libraries to coexist in the classpath at the same time (as long as they are internal helpers for a bundle, not part of the bundle’s API).
The bundle-activator feature also allows code within a jarfile to be executed as soon as the jarfile is loaded (technically, when a bundle is “started”). This is a powerful feature that does not exist in standard Java; the closest is class static initialisers but “activators” have access to the OSGi environment too.
See this article on the OSGi classloading model for further details.
OSGi Bundle Lifecycle
OSGi defines several possible states for a bundle:
INSTALLED - A bundle jarfile has been registered, but is not yet useable by any other bundle because one or more of the
Import-Packagerequirements it declares cannot (yet) be satisfied.
RESOLVED - A bundle jarfile has been registered and all its
Import-Packagerequirements are available. Other bundles can now see classes exported from this bundle.
ACTIVE - All “services” (ie singleton objects) provided by the bundle are now available for use. This means that the class specified by the Bundle-Activator has successfully run, and any other code that handles “bundle start events” has also successfully completed.
The fact that a bundle can sit in an “installed but not resolved” state solves many problems regarding startup ordering; bundles do not have to be loaded in a “correct” order as a bundle loaded “too early” (before its dependencies are available) just waits until they are available.
OSGi provides an “event bus” through which changes to the OSGi environment (and particularly the installation and activation of bundles within the environment) are announced. Code in OSGi bundles can register to receive events, making it possible for bundles to “manage” other bundles - ie to effectively extend the core OSGi functionality simply by installing bundles.
An OSGi runtime provides a “service registry”, which has a mapping of (java-interface, optional-props) -> (list of objects implementing that interface).
Whereas the OSGi import/export functionality allows one bundle to find code exported by another bundle, the OSGi services functionality allows one bundle to find objects exported by another bundle. Or to express it a different way, OSGi provides a way to obtain references to singleton objects exposed by a bundle (jarfile), via either active “lookup”, by having them injected, or by registering a filter and receiving callbacks for singleton objects matching the filter criteria (ie OSGi provides a service “event bus”).
The service instances can be created and registered in several ways, including the following:
- The class pointed to by the
Bundle-Activatormanifest entry can create and register them
- Some other bundle can listen for “bundle start” events, and then inspect files within the to-be-started jarfile in order to determine which classes to instantiate, and which instances to register with the service-registry. There are two standard OSGi bundles that provide such a feature (“declarative services” and “blueprint” can both read xml configuration files specified in manifest properties); other libraries which are not part of the official OSGi spec also exist which do similar things.
In all cases, the interface that the service object implements is defined in either:
- an “exported” package of the same bundle, or
- some other “api” bundle
In the first case, there can only ever be one bundle providing services of that type; the second is more common as it allows “pluggable” implementations where the implementing bundle can be swapped out for another, or even where multiple bundles concurrently provide implementations and “users” of the service can choose which one they want (via service properties) or invoke all available instances.
In the default case, the object-reference returned by a “lookup” of a service in the OSGi registry is a direct Java reference to the real implementing object, ie no proxies or marshalling is involved; invoking the service is just as efficient as invoking a normal Java object. There are some cases where a proxy is used: for declarative-services explicitly marked as “lazy”, and when using the OSGi “Blueprint” approach to obtaining service references.
Because users of OSGi services obtain them via lookups of interfaces, it is also fairly simple to build a distributed system where lookup returns a proxy object that transparently invokes a method over the network.
There are a number of mechanisms available to allow bundles to delay registering their services until other services they depend on are available. Doing this correctly solves many problems regarding startup ordering; bundles “started” too early (before bundles providing their required services are started) simply don’t publish their “unsatisfied” services until the required services appear.
Objects published as “services” do not need to implement any particular interface; any Java object at all can be published as a service (including java.lang.String or java.sql.DataSource if desired).
See this article on OSGi services for further details.
OSGi Permissions Model
The Java SecurityManager class can be configured to grant code certain permissions based upon the jarfile it was loaded from. While technically this permission model can be dynamic (ie changed during application runtime) this is not easy.
The OSGi ConditionalPermissionsAdmin service is a standard OSGi bundle which:
- adds new types of permissions, eg which bundles can register a service with a particular id, or which bundles can obtain references to specific services;
- allows granting of permissions based upon bundle attributes;
- allows permissions to be defined in Java code - often more convenient than an external text-file;
- allows permissions to be changed as bundles are registered/deregistered;
- allows bundles to embed their own security rules, and for system configuration to optionally add extra constraints (android-style permission system)
See this article on OSGi permissions for further details.
Bundles may need configuration parameters to specify how they are expected to behave, eg which port to listen on. OSGi defines a standard OSGi service that bundles can use to query configuration data. There is also an API for registering an interest in “config change events”, where a bundle can receive an event if configuration changes at runtime.
The underlying data may come from properties files on the local disk. However it is also possible for one bundle to update properties of another bundle at runtime, or for an administrator to update a bundle’s properties by editing config files or using an OSGi console. If the bundle has configured itself to receive config “events” then the bundle is notified of these changes.
See this article on OSGi Configuration for further details.
OSGi Event Broker
There is a standard OSGi bundle which implements an in-memory event-broker through which arbitrary events can be published by one bundle, and received by all other bundles which have registered an interest in events of that type. While similar in functionality to the OSGi core event system that notifies subscribers about bundle and service changes, it is a separate service.
OSGi Compendium Services
In the OSGi “compendium” specification there are dozens of standard APIs defined, and a “full” OSGi environment provides bundles that implement these APIs (ie expose singleton service objects which user bundles can look up or otherwise access). A few of the features described above are technically part of the “compendium” (Declarative Services, Blueprint, Event Broker). Some other useful compendium services include:
HttpService: Exposes an implementation of a Java Servlet Engine at a specific URL. This allows an OSGi environment to be configured with a webserver bundle running on a specific port; other bundles can then “contribute” servlets to the common servlet engine, ie the website.
RemoteService: Exposes OSGi services objects for invocation via RMI or other protocols; an OSGi service with the “service.remote” property set is automatically made available on the port configured for the RemoteService bundle.
See the compendium specification for more details.
What is OSGi Useful For?
In all the detail above, it is possible to lose sight of the big picture: what is OSGi good for? Actually, it is already in use for a very wide range of purposes.
The Eclipse RCP (rich client platform) since version 3.1 is built around an OSGi implementation (Equinox). Many desktop apps have been built using RCP, including the Eclipse IDE for developers. Different components (both graphical and “back end”) are implemented as OSGi bundles which publish OSGi services; components communicate with each other via both OSGi service interfaces and events passed through the OSGi Event Broker. This design allows plugins to theoretically be downloaded and activated at runtime without restarting the application. It significantly reduces coupling between components, making Eclipse-based applications more an extensible “swarm of cooperating components” than a traditional tightly-integrated whole.
Many J2EE environments are now OSGi-based. Each “webapp” or “ear” is a separate OSGi environment, removing any possible interactions between different webapps running in the same JVM. Different parts of the large J2EE spec are separate optional bundles; things like JTA can be present or not and code can detect that by using standard OSGi service lookup.
Many embedded systems are OSGi based, with support code for optional hardware modules being handled by different OSGi bundles; when the hardware is not present then that bundle is not installed or not started.
As can be seen, OSGi use-cases cover the spectrum from small to large systems.
OSGi bundles are just jarfiles, objects exposed as OSGi services are just POJOs, and techniques like Declarative Services allow OSGi services from other bundles to be “injected” without using any OSGi-specific code. It is therefore possible to write jarfiles that can be used in an OSGi environment to provide classes and services, but are equally valid as Java libraries in a normal non-OSGi application. Some features of OSGi are not accessable without explicitly using OSGi APIs (eg the Event Broker), but the vast majority is.
Many OSGi features have similarities to features of operating systems:
The import/export packages support has similarities to the way “shared libraries” are handled by operating systems such as Unix or Windows. Such binary libraries have headers that list functions and variables that are “runtime linkable”, but many variables and functions defined in the original source code are not in that list. Libraries also have version-numbers.
The way services start only after their dependencies are started, and the way OSGi notifies bundles of changes in their dependencies is somewhat similar to the behaviour of Linux systems “systemd” or “upstart”.
Event Brokers are a well known IT pattern, and the OSGi event bus is simply an efficient in-JVM implementation