|
Qwylt | |||||||||
| PREV NEXT | FRAMES NO FRAMES | |||||||||
Existing module systems may be wrapped by this abstraction layer without modification; however, to enable interoperation, their dependency resolution algorithms must be updated to use the provided abstractions.
The framework complements but does not require the 'module' access keyword feature proposed in
JSR 294.
Syntax Agnostic Types
The framework makes no assumptions about syntax or data structure in a module system, and does no parsing;
instead, module system implementations map their internal representations into a canonical type provided by
the framework. For example, version numbers are mapped to the Version
type, which supports ordered comparison but cannot be constructed directly from a String. Version
ranges and
expressions are supported in a similar fashion, as is all
module metadata.
Storage and Discovery
Module metadata and content are accessed via the ModuleArchive
type—content as ModuleResources.
Archives are installed into a ModuleRepository, which
supports discovery using a simple, extensible (and indexable) query
mechanism.
A repository instance will normally store archives from a single module
system; however, multiple repositories can be federated so that a search may find results in any
number of module systems.
Repositories do not use a fixed "parent-delegation" model; rather, each instance is constructed with a
policy type that enables any form of delegation and/or
result filtering.
Services Model
TBD
Composition
Modules are generally loosely coupled to their dependencies, and so may have their classes defined by separate
ClassLoader instances. However, in some cases it is desirable to partition one logical module into
multiple physical ones, and have them joined into the same ClassLoader at runtime (e.g. package
private classes or localization resources). The framework supports such "composites" by:
ClassLoader:
ClassLoader.
ClassLoader instance. This is
partially just convenience but is also a direct consequence of the
JVM definition of the
package private (default) access mode, where a "runtime package" is defined to be both the package name and a
definining ClassLoader.
ClassLoaders provide a name space for classes. This "class space" normally extends beyond a single
loader instance to include the class space(s) formed by other loaders to which that instance will delegate. The
concept
of a class space is directly represented in the framework by the
ClassSpace type, and can be thought of as a view onto the set of all
classes reachable from a given loader instance.
In a standard Java SE environment, partitioning of classes into separate ClassLoader instances is
coarse
grained, with most application classes lumped into a single "system" loader instance via the "classpath". In a
modular system, partitioning is fine grained, allowing a great deal of flexibility in composing systems,
and enabling multiple versions of modules to exist in the same process. However, this flexibility implies a
significant
increase in the number of ClassLoader instances in a single process.
Increasing the number of ClassLoader instances raises the likelihood of a category of errors that are
rarely seen in an SE environment, but are well known in EE and modular systems: duplication errors. A duplication
error (LinkageError or ClassCastException) can occur whenever there is more than one
Class instance with the same name—which can only exist with multiple loaders—and are
extremely difficult to diagnose and correct (see Class Visibility Errors
for
more detail).
The OSGi alliance addressed this problem in the R4 release of the core specification, describing a model for detecting class space "consistency" which this framework has adopted: each package exported by a module must declare the set of packages on which it depends (see ExportedPackage.getDependentPackages()). With this information, the framework is able to guard against duplication errors, either by choosing a common provider from multiple choices or failing early when one cannot be found (again, see Class Visibility Errors for more detail).
A ClassSpace is "consistent" when there is a single provider for each package. More formally:
The framework spi provides a general model for imposing such constraints
(ImportConstraint), and a specific implementation for this
one: SharesDependentPackages.
Connection ("dependency resolution")
A module may contain types that are private to that module as well as types which are public and
accessible to other modules; the latter are said to be "exported". Rather than a complete list of all
exported types, a convenient shorthand is used instead: each package that contains one or more public
types is listed as an exported package. A module may then
declare a set of imports (dependencies), where each is either an
individual package or an
entire module (shorthand for "all exported packages").
ModuleArchive provides accessor methods for both exports and
imports.
Import declarations may specify an exact version, a version range or version expression, and may even require specific attributes; together these form a constraint that the runtime must use to select a provider. The process of connecting each import declaration with a specific provider is often referred to as "dependency resolution"; the framework uses the term "connection".
Connection is the main function of the framework spi, ensuring either that all dependent classes are visible to a module or that a connection error is thrown before first use. The canonical types of the api provide a module system independent model on which the spi classes can act; the spi classes provide a default implementation of the constraint solving machinery that results in connection or failure.
The connection process must be orchestrated to ensure that multiple threads calling getModule() cannot deadlock. Orchestration is performed by a singleton ConnectionCoordinator, with a default implementation that uses a single thread for all processing. Each archive maintains connection state in a ModuleState instance, an abstract type which is implemented by specific module systems. The ModuleArchive.getModule() method submits the state to the coordinator, which then drives it and the state of any import candidates in a two-phase commit style process.
The connection process proceeds as follows:
ClassLoader to delegate requests to the imported Module
instances; the framework spi contains a standard implementation of this usage pattern:
ModuleClassLoader.
|
Qwylt | |||||||||
| PREV NEXT | FRAMES NO FRAMES | |||||||||