• No results found

3. Bundle Dependencies

3.6. Requiring a Bundle

Import-Package, have exactly the same name as OSGi manifest headers, but they are treated quite differently.

We can also explicitly add and remove packages from the imports: # b n d s a m p l e

I m p o r t−P a c k a g e: !o r g.f o o, \ c o m.b a r, \ ∗

This results in org.foo being excluded from the imports list, even if bnd detects a dependency on it. This is dangerous, but useful if the dependency exists only in a part of the code that we know can never be reached. The second entry inserts the package com.bar into the imports list, whether or not bnd is able to detect a dependency on it. This is mainly useful in cases where the bundle code uses reflection to dynamically load classes. Note that only fully named packages can be used: if we wrote com.bar*, that would be a pattern, and only included if a package starting with com.bar were to be found in bytecode analysis.

Note that the final asterisk is very important. If you omit it then only the dependencies listed explicitly will be included in the generated manifest. Any others that bnd finds will be excluded, which is generally not what one wants to achieve.

3.6. Requiring a Bundle

Another kind of constraint that can be placed on a bundle is theRequire-Bundle header. This is similar to Import-Packageheader in that it makes exported packages from another bundle available to our bundle, but it works on the whole bundle rather than individual packages:

R e q u i r e−B u n d l e: m a i l b o x−a p i

If we represent a required bundle as follows:

required bundle

Then the runtime resolution of a bundle using Required-Bundle looks like Figure 3.2. In this figure, Bundle B has a Require-Bundle dependency on Bundle A, meaning that Bundle B cannot resolve unless Bundle A is in RE- SOLVED state.

The effect at runtime is as if Bundle B had declared anImport-Packageheader naming every package exported by Bundle A. However, Bundle B is giving up

Bundle A org.sample.foo org.sample.bar org.sample.baz ... A Bundle B

Figure 3.2.: The runtime resolution of a Required Bundle

a lot of control, because the list of imports is determined by the set of packages exported by Bundle A. If additional exports are added to Bundle A, then they are automatically added as imports to Bundle B.

The use ofRequire-Bundleis strongly discouraged by most OSGi practition- ers except where absolutely necessary, because there are several flaws with this kind of dependency scheme.

First, a bundle usingRequire-Bundleto import code from another bundle is at the mercy of what is provided by that bundle — something that can change over time. We really have no idea what packages will be provided by another bundle at any point in the future, yet nevertheless our bundle will successfully resolve even if the required bundle stops exporting some functionality that we rely on. The result will almost certainly be class loading errors such as ClassNotFoundExceptionor NoClassDefFoundErrorarising in bundles that were previously working.

The second and closely related problem is that requiring bundles limits our ability to refactor the composition of those bundles. Suppose at some point we notice that Bundle A has grown too big, and some of the functionality it provides is really unrelated to the core and should be separated into a new bundle, which we will call Bundle A0. As a result, some of the exports of A move into A0. For any consumers of that functionality who are using purely Import-Package, the refactoring ofA into A and A0 will be entirely transparent: the imports will simply be wired differently at runtime by the framework. Figures3.3 and 3.4 show the “before” and “after” states of per-

3.6 Requiring a Bundle 59

forming this refactoring where Bundle B depends on the packages of Bundle A using Import-Package. After refactoring, BundleB will continue to work correctly because it will still import all the packages it needs – and it will be oblivious of the fact that they now come from two bundles rather than one.

org.package1 Bundle A org.package2 org.package1 Bundle B org.package2

Figure 3.3.: Refactoring withImport-Package: (Before)

org.package1 Bundle A org.package1 Bundle B org.package2 org.package2 Bundle A'

Figure 3.4.: Refactoring withImport-Package: (After)

However, Figures3.5and3.6show the “before” and “after” states when Bundle B requiresBundleAusingRequire-Bundle. After refactoring, BundleB will no longer import one of the packages it needs, but it will still be able to enter the RESOLVED state because theRequire-Bundleconstraint is still satisfied. Therefore we are likely to get NoClassDefFoundErrors whenB attempts to use classes fromorg.package2.

Third, whole-module dependency systems tend to cause a high degree of “fan- out”. When a bundle requires another bundle, we have no idea (except by low- level examination of the code) which part of the required bundle it is actually using. This can result in us bringing in a large amount of functionality when only a small amount is really required. The required bundle may also require several other bundles, and those bundles require yet more bundles, and so on. In this way we can easily be required to pull in fifty or more bundles just to resolve a single, small bundle. Using purely Import-Packagegives us the opportunity to break this fan-out by finding instances where only a small portion of a large bundle is used, and either splitting that bundle or finding

org.package1 Bundle A

org.package2

Bundle B

A

Figure 3.5.: Refactoring withRequire-Bundle: (Before)

Bundle B A org.package1 Bundle A org.package2 Bundle A'

Figure 3.6.: Refactoring withRequire-Bundle: (After)

an alternative supplier for the functionality we need.

Essentially, usingRequire-Bundleis like grabbing hold of the wrapper around what we need, rather than the contents itself. It might actually work if JARs in Java were more cohesive, and the constituent packages did not change over time, but that is not the case.

Require-Bundle was only recently introduced into OSGi in Release 4, and given all the problems listed, it may seem mysterious that it was introduced at all. The main reason was to support various legacy issues in Eclipse, which abandoned an earlier module system in favour of OSGi. The pre-OSGi module system used by Eclipse was based on whole-module dependencies, and if OSGi had offered only Import-Package then the challenges of making thousands of existing Eclipse plug-ins work as OSGi bundles would have been insur- mountable. Nevertheless, the existence of Require-Bundle remains highly controversial, and there are very, very few good reasons ever to use it in new bundles.