Chapter 2. Importing and Exporting Packages
Abstract
The key to understanding the rules for importing and exporting packages is to know what type of bundle you are dealing with. This chapter aims to show that, once you have figured out the role played by a particular bundle, you will quickly be able to figure out the requisite Maven bundle configuration.
2.1. Overview of Bundle Types
Overview
The Maven bundle plug-in is a powerful tool for generating OSGi bundle header information. When set up correctly, it can generate most of the bundle header information for you automatically. The bundle plug-in has, however, a bewildering array of options available. In order to make sense of the bundle plug-in configuration, the first question question you need to answer is: what type of bundle do I have?
Figure 2.1, “Relationship between API, Provider, and Consumer Bundles” shows the most important bundle types and bundle relationships that you will come across in a typical OSGi application.
Figure 2.1. Relationship between API, Provider, and Consumer Bundles
A well-designed application normally exposes a block of functionality through a collection of abstract Java interfaces, which constitute a pure API package. To provide a clean separation of implementation and interface description, the API package is normally deployed in one bundle, the API bundle, and the API implementation is deployed in a separate bundle, the provider bundle. The code that actually uses the API is referred to as a consumer and is packaged in a consumer bundle. Each of these bundle types have differing requirements when it comes to configuring their Maven bundle plug-in instructions.
Bundle Types
We can identify the following typical bundle types:
- Library bundle
- A library bundle contains Java classes and interfaces, which are public and intended to be used by other bundles. Often, in a library, there is no formal separation between API interfaces and implementation classes. Instead, developers simply instantiate and use the classes provided by the library.A library bundle does not publish or access any OSGi services.
- API bundle
- A pure API bundle contains only Java interfaces (or abstract classes), which are public. The implementation of the API is meant to be provided in a separate bundle or JAR.An API bundle does not publish or access any OSGi services.
- Provider bundle
- A provider bundle contains the classes that implement an API. Usually, the classes in a provider bundle are all private and are not exported from the bundle.The natural mechanism for a provider bundle to expose its functionality is to create and publish one or more OSGi services (where the OSGi services are then accessed through the public API).
- Consumer bundle
- A consumer bundle contains code that uses an API.A consumer bundle typically accesses one or more OSGi services; it does not usually publish an OSGi service itself (unless it is acting as a hybrid of a consumer and a provider).
- API/provider combination bundle
- In some cases, it can make sense to package API packages and implementation packages together, resulting in an API/provider combination bundle. For example, if you intend to provide only one implementation of an API, it can be simpler to package the API and its implementation in a single bundle.For the API/provider combination bundle, it is assumed that the API code and the implementation code belong to the same Maven project.
- API/provider build-time combination bundle
- Even if you opt for a formal separation of API code and implementation code—that is, where the API code and the implementation code are developed in separate Maven projects—you might nevertheless want to add the API interfaces to the provider bundle. Peter Kriens (creator of the Bnd tool, on which the Maven bundle plug-in is based) recommends that a provider bundle should always include its API interfaces. One reason for this is that, when you deploy a provider bundle without embedding the API, there always a risk that the required version of the API will not be available in the OSGi container at deploy time. By embedding the API interfaces in the provider, you eliminate that risk and improve the reliability of your deployment.When you develop your API interfaces and your implementation classes in separate Maven projects, the bundle plug-in supports a special configuration that enables you to embed the API in the provider bundle at build-time. Hence, this bundle is called an API/provider build-time combination.
Summary of import/export rules
Table 2.1, “Import/Export Rules for Bundles” summarizes the import/export rules for the various bundle types.
Bundle Type | Export Own Packages | Import Own Packages | Publish OSGi Service | Access OSGi Service |
---|---|---|---|---|
Library | Yes | |||
API | Yes | |||
Provider | Yes | |||
Consumer | Maybe | Maybe | Yes | |
API/provider combination | Yes (API only) | Yes (API only) | Yes |
Importing and exporting own packages
The basic rule for exporting a bundle's own packages is as follows: a bundle should export any of its own packages that are public and intended to be used by other bundles; private packages should not be exported.
The basic rule for importing a bundle's own packages is as follows: a bundle does not import any of its own packages, because those packages are already available from the bundle's own classpath (external dependencies, on the other hand, must of course be imported).
But there is a special case where it makes sense for a bundle both to export and to import its own packages. The OSGi container interprets this in a special way: the bundle either imports the package (thus using the classes from another bundle, and ignoring its own copy of those classes) or exports the package (thus using its own copy of the classes and making these classes available to other bundles).
For example, an API/provider combination bundle exports and imports its own API packages. This ensures the bundle is able to use the API interfaces from another bundle, if a suitable version is available. Re-using existing packages and classes is an important strategy to avoid duplicate class instances in the same class space (see Section 1.2, “Conflicting Classes”).
When to publish an OSGi service
An OSGi service is essentially a plain Java object, made accessible to other bundles by being published in the OSGi service registry (see section "Exporting a Service" in "Deploying into the Container").
Provider bundles demonstrate a typical use case of an OSGi service. The classes in a provider bundle are only meant to be accessed indirectly—that is, through the relevant API interfaces. But there must be some mechanism that enables you to instantiate one or more of the implementation classes, in order to bootstrap the implementation. Typically, the mechanism for bootstrapping an implementation depends on the framework or container you are currently using. For example, in Spring you would typically use a
bean
element to create an instance of an implementation class.
In the context of OSGi, however, the natural mechanism for bootstrapping an implementation is to create and publish one or more OSGi services.
Default bundle plug-in settings
Example 2.1, “Maven Bundle Plug-In Settings” shows a minimal configuration of the Maven bundle plug-in, which you can use for basic Maven projects.
Example 2.1. Maven Bundle Plug-In Settings
<project ... > ... <build> ... <plugins> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <extensions>true</extensions> <configuration> <instructions> <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName> <Import-Package>*</Import-Package> </instructions> </configuration> </plugin> ... </plugins> </build> </project>
Although the preceding minimal configuration is not ideal for all bundle projects, it does have a useful default behavior that is often good enough to get your project started:
- Import rules—when you build your Maven project, the bundle plug-in tries to discover all of the bundle's package dependencies automatically. By default, the bundle plug-in then adds all of the discovered package dependencies to the
Import-Package
header in the Manifest file (equivalent to the wildcard instruction,*
).However, the effectiveness of the initial scan depends on where the dependencies actually arise, as follows:- Java source code—Bnd scans the Java source code in your project to discover package dependencies. There is one blind spot in this process, however: the scan cannot detect dependencies that arise from using Java reflection and explicit calls on a class loader.
- Spring configuration file—Bnd is not capable of scanning Spring configuration files to discover package dependencies. Any dependencies arising from Spring configuration must be added manually to the
Import-Package
instruction. - Blueprint configuration file—the Apache Karaf implementation of blueprint is capable of mapping XML namespaces to Java packages and dynamically resolving the resulting dependencies at run time. Hence, there is no need for blueprint's dependencies to be added to the
Import-Package
instruction.This is one of the major benefits of using blueprint configuration.
Provided that the dependent bundles define a version for their exported packages, the bundle plug-in also automatically assigns a version range to the imported packages (see Section 3.3, “Automatic Import Versioning”). - Export rules—by default, the Maven bundle plug-in exports all of the packages in your project, except for packages that match the patterns
*.impl
or*.internal
. It is therefore recommended that you follow the convention of naming all of your private packages with the suffix*.impl
or*.internal
.By default, the bundle plug-in does not assign a version to the exported packages. This is a major disadvantage of the default export instruction (see Section 3.2, “Export Versioning”).