5.5.3 True Java components
While JAR files cannot in themselves be considered true components, Java does ship with a proper, if domain-specific, component model in its Enterprise Edition. Enterprise JavaBeans (EJBs) are hosted by an application server and have access to enterprise services, often by setting declarative attributes. An EJB entity bean can, for instance, declaratively request that the application server should handle its database access by transparently storing and loading its state. (Entity beans represent persistent data typically stored in a relational database (Mukhar et al. 2005).)
EJBs cannot interfere with each other in the manner described in the previous section as their containers do have runtime identities. EJBs achieve this using custom class loaders. Class loaders are Java classes that descend from java.lang.ClassLoader and are responsible for loading classes and interfaces from compiled bytecode. A typical class loader loads classes from the file system, but custom class loader can also load classes from, say, in-memory data structures or over a network. Internally, a Java virtual machine represents a class or interface using not only its given compile-time name, but also using the class loader responsible for loading it (Lindholm and Yellin 1999). A Java runtime name thus consists of a pair: its compile-time name and its defining class loader. This allows a virtual machine to differentiate between two objects that share the same class and package names but were loaded using two different class loaders. A class loader is expected to delegate calls to a parent class loader before it tries to load a class itself. Class loaders thus often form an implicit tree. At the root of this tree is the “bootstrap” class loader, which loads classes from Java’s core runtime system. The bootstrap class loader is thus always responsible for loading the definitions of core classes such as java.lang.Object (from which all classes ultimately descend).
Class loaders are a very useful mechanism on which to build true Java components, and as class loaders are normally implemented entirely in Java, there is no need to resort to native code. Corwin et al. (2003) present such a system, MJ, which attempts to solve the two fundamental problems discussed in section 5.5.2. MJ components must list their dependencies and what packages they provide as part of their metadata. Using custom class loaders, MJ ensures that dependent types are available (by simply calling on the services of the class loader associated with the dependent component), and that types that are not explicitly exported from a given component are not made available to the outside world. (When MJ is used, the class loaders in the system no longer form a tree; they form a directed acyclic graph, as class loaders can delegate calls to an arbitrary number of other class loaders, representing components which the current component is dependent on.)
In industry, the ideas of MJ are best embodied by a popular Java component model named OSGi. OSGi components are called “bundles,” and are deployed as ordinary JAR files, with the sole difference being that the manifest file holds additional metadata similar to that mandated by MJ, such as lists of dependencies and of packages that are exported from the bundle. As an OSGi bundle is a standard JAR file, it can be loaded both by a specialized OSGi runtime system (enabling it to be used as a true component), and in the traditional fashion. OSGi defines a rigid versioning scheme, allowing a bundle to require a specific version of another bundle, or declaring that it can accept a range of acceptable versions.1
OSGi bills itself as the “dynamic module system for Java,” meaning that bundles can be loaded and unloaded at runtime. This is important to application servers, which should suffer as little downtime as possible. The dynamic nature of OSGi makes it possible to install a new version of a bundle without taking down the server, and without affecting other bundles. To achieve this, OSGi defines a strict life cycle for bundles.
In contrast to other component models studied, OSGi only attempts to enable software components that run in-process (that is, bundles that all run in the same virtual machine). A distributed version is reportedly in the works (Taft 2008).
OSGi bundles adhere perfectly to the definitions of Chapter 1. Bundles are protected from one another, enabling multiple versions to co-exist, and declare their dependencies and what they themselves provide declaratively. OSGi is cleanly layered on top of the Java platform, and as such, OSGi bundles are usable in binary form on any platform for which a Java implementation exists. A handful of commercial and open source implementations are available. As of the middle of 2009, OSGi is making its way through the Java Community Process, and may become a standard part of a future version of the Standard Edition of Java.
- The recognized OSGi “best practice” is not to request that a specific, named bundle be present, but to import a particular version of a package, making it possible to move packages from one bundle to another without unduly disturbing dependent bundles (Bartlett 2009:60).