Proposal (revised^2): #ReflectiveAccessToNonExportedTypes & #AwkwardStrongEncapsulation: Open modules & open packages
mark.reinhold at oracle.com
mark.reinhold at oracle.com
Thu Oct 27 15:53:36 UTC 2016
Issue summary
-------------
#ReflectiveAccessToNonExportedTypes --- Some kinds of framework
libraries require reflective access to members of the non-exported
types of other modules; examples include dependency injection (Guice),
persistence (JPA), debugging tools, code-automation tools, and
serialization (XStream). In some cases the particular library to be
used is not known until run time (e.g., Hibernate and EclipseLink both
implement JPA). This capability is also sometimes used to work around
bugs in unchangeable code. Access to non-exported packages can, at
present, only be done via command-line flags, which is extremely
awkward. Provide an easier way for reflective code to access such
non-exported types. [1]
#AwkwardStrongEncapsulation --- A non-public element of an exported
package can still be accessed via the `AccessibleObject::setAccessible`
method of the core reflection API. The only way to strongly
encapsulate such an element is to move it to a non-exported package.
This makes it awkward, at best, to encapsulate the internals of a
package that defines a public API. [2]
Proposal
--------
(This is the third proposal for #ReflectiveAccessToNonExportedTypes.
The note that follows [3] summarizes the background and relates the
alternatives presented thus far.)
Extend the language of module declarations with the concept of _open_
modules. Open modules make it easy to define modules whose internals
will be accessed at run time by reflection-based frameworks. Every
package in an open module has the following properties, by default:
(1) At compile time it is not exported.
(2) At run time it is exported without regard to split-package
conflicts or other inconsistencies, i.e., it does not affect
the operation of the resolver, nor the construction of layer
configurations, but is simply exported as if by invoking the
`Module::addExports` method during layer instantiation.
(3) At run time it is available for _deep reflection_, i.e., all
of its elements are accessible via the core reflection API
including non-public elements, which can be accessed via the
`AccessibleObject::setAccessible` method.
A package in an open module can be exported explicitly, for use at
compile time, via the familiar `exports` directive, so property (1) will
not hold. In that case it will also partake in run-time resolution, so
property (2) will not hold either.
Suppose we have a module `foo.bar` that contains an API package
`com.foo.bar`, whose public types are intended for use by other modules,
and a non-API package `com.foo.bar.model`, that contains entity classes
to be manipulated by Hibernate via core reflection. Then the module
declaration
open module foo.bar {
exports com.foo.bar;
requires hibernate.core;
requires hibernate.entitymanager;
}
makes all elements, public and otherwise, of all packages available for
deep reflection at run time, but makes only the public and protected
types in `com.foo.bar` accessible at compile time. Thus Hibernate can
access non-public elements of the `com.foo.bar.model` package via the
`setAccessible` method, and some other module that `requires foo.bar`
can be compiled against the API in the `com.foo.bar` package but not
against the types in any other package.
Open modules can be considered an intermediate step on the migration
path, between automatic modules and normal modules:
- An automatic module offers the traditional level of encapsulation:
All packages are both open for deep reflective access and exported
for ordinary compile-time and run-time access to their public types.
- An open module offers a modest degree of stronger encapsulation: All
packages are open for deep reflective access, but the module's author
must specify which packages, if any, are exported for ordinary
compile-time and run-time access.
- A normal module offers the strongest encapsulation: The module's
author must specify which packages, if any, are open, or exported,
or both.
An open module is a good starting point for application code that's being
modularized by its author. It affords a separation of concerns, so that
the author can focus on defining the module's domain-specific API without
having to worry about how the module will be inspected or manipulated at
run time by reflective frameworks.
* * *
To open specific packages in normal module declarations we introduce a
new per-package directive, `opens`. A package in a normal module can be
opened, or exported, or both, as follows:
- If a package is only opened (`opens p;`) then properties (1), (2),
and (3) hold, i.e., its types are not accessible at compile time,
it does not affect resolution, and all of its elements are available
for deep reflection at run time.
- If a package is only exported (`exports p;`) then those properties
do not hold, and it is exported exactly as it is today.
- If a package is both exported and opened (`exports p; opens p;`)
then it is exported as today and is, additionally, available for
deep reflection at run time (3).
We can use the `opens` directive to refine the previous example so that
only the `com.foo.bar` package is accessible at compile time, only the
`com.foo.bar.model` package is available for deep reflection at run time,
and the non-public elements of all other packages are strongly
encapsulated:
module foo.bar {
exports com.foo.bar;
opens com.foo.bar.model;
requires hibernate.core;
requires hibernate.entitymanager;
}
It's possible to both open and export a package in a normal module, but
it's often inadvisable. This is especially so for API packages, since
normally an API's internal implementation details should be strongly
encapsulated. This combination of directives may, however, be useful for
legacy APIs whose internals are known to be accessed by existing code.
To ensure the integrity of the platform we expect that all the modules of
the JDK itself will be normal modules and that very few, if any, packages
will be opened.
The `opens` directive cannot be used in an `open` module, since all
packages in such modules are implicitly open.
* * *
Both the `exports` and `opens` directives can be qualified, so that a
package is exported or opened only to certain other named modules. As
before, duplicate directives are not permitted in order to ensure easy
readability. At most one `exports` directive is relevant to any given
package, and at most one `opens` directive is relevant to any given
package.
There is no syntax for wildcards. If the defaults are not sufficient for
a package then the package must be named explicitly.
* * *
The existing syntax of `requires public` has long been confusing, so we
here take the opportunity to fix that problem by renaming the `public`
modifier in `requires` directives to `transitive`. Thus the declaration
module foo.bar {
exports com.foo.bar;
requires public java.sql;
}
is now written
module foo.bar {
exports com.foo.bar;
requires transitive java.sql;
}
This is potentially confusing in a different way, since in mathematics
the term "transitive" is usually applied to an entire relation rather
than to three specific elements of a set. Its use here does not, in
particular, mean that the resolver does not interpret plain `requires`
directives when computing the transitive closure of a set of root
modules. "Transitive" as used here is in the more abstract sense,
expressing the notion of conveying a property -- in this case, the
readability of the required module -- from one thing to another. On
balance, however, `transitive` does appear superior to `public`.
Notes
-----
- This proposal is significantly different from both the first and
second proposals [4][5]. It recognizes that, in practice, it will
be common to have open modules, all of whose content is available
for deep reflection but otherwise inaccessible at compile time unless
explicitly exported. If finer-grained control over whether a package
is opened, exported, or both is required then a normal module
declaration can be used.
- This proposal is similar to one made by Stephen Colebourne [6],
in which he uses the term `exposes`, and to various unpublished
proposals (of which there have been many!). We here adopt the term
`opens` rather than `exposes` in order to avoid confusion between
the phonetically-similar `exports` and `exposes`, especially for
non-native speakers of English. We also use `open` to modify the
`module` keyword rather than introduce wildcards for the `opens`
directive, since the former is easier to explain. The latter would,
further, lead people to expect wildcards for the `exports` directive,
but we'd prefer to avoid that since it could encourage the careless
exportation of all of a module's packages. Exporting a package for
use as an API should always be an explicit, affirmative choice.
- A separate `opens` directive, and the avoidance of wildcards,
simplifies the interactions amongst qualified and unqualified
package-level directives. We no longer need complex rules such as
those shown for the `private` modifier in the previous proposal [5].
- This proposal implies corresponding changes to the `ModuleDescriptor`
and `Module` APIs, but these are reasonably straightforward.
- This proposal amends the proposal for #ResourceEncapsulation [6]
to replace the use of private exports with open modules and open
packages. Wherever that proposal indicates that a resource can be
located when its effective package name identifies a package that's
exported privately, assume instead that the resource can be located
when that package is either an element of an open module or an open
package in a normal module.
- If a container is to ensure that a package in an application module
is available for deep reflection only by a trusted framework module
then it can arrange for that by rewriting that module's descriptor,
as suggested previously [8], to insert the appropriate qualified
`opens` directive. This works even in the case, pointed out by Jason
Greene [9], of two modules that open a package of the same name to
the same framework module, since opened and unexported packages are
not subject to the usual package-consistency checks. There is no
need any longer for the resolution algorithm to take this type of
scenario into account [a]. A more flexible way to arrange for such
precisely-constrained access is a separate issue and may be addressed
by a future proposal.
- This proposal primarily addresses "friendly" uses of reflection, such
as dependency injection and persistence, in which the author of a
module knows in advance that one or more, or possibly all, packages
must be opened at run time for deep reflective access by frameworks.
Intrusive access to arbitrary packages of arbitrary modules by, e.g.,
serialization frameworks or debugging tools, will still require the
use of sharp knives such as `--add-exports` or `--add-opens`
command-line options, the legacy unsupported `sun.misc.Unsafe` API
and related APIs, or JVM TI.
- Using `--add-exports` or `--add-opens` command-line options, or their
equivalents, remains awkward, but sometimes they're the only way out.
To ease migration it's worth considering some way for an application
packaged as a JAR file to include such options in its `MANIFEST.MF`
file, as suggested by Simon Nash [b]. This is addressed in the
proposal for #AddExportsInManifest [c], which is hereby amended to
rename the `Add-Exports-Private` attribute to `Add-Opens`.
[1] http://openjdk.java.net/projects/jigsaw/spec/issues/#ReflectiveAccessToNonExportedTypes
[2] http://openjdk.java.net/projects/jigsaw/spec/issues/#AwkwardStrongEncapsulation
[3] http://mail.openjdk.java.net/pipermail/jpms-spec-experts/2016-October/000431.html
[4] http://mail.openjdk.java.net/pipermail/jpms-spec-experts/2016-June/000307.html
[5] http://mail.openjdk.java.net/pipermail/jpms-spec-experts/2016-September/000390.html
[6] http://mail.openjdk.java.net/pipermail/jpms-spec-experts/2016-September/000392.html
[7] http://mail.openjdk.java.net/pipermail/jigsaw-dev/2016-September/009370.html
[8] http://mail.openjdk.java.net/pipermail/jigsaw-dev/2016-July/008637.html
[9] http://mail.openjdk.java.net/pipermail/jigsaw-dev/2016-July/008641.html
[a] http://mail.openjdk.java.net/pipermail/jigsaw-dev/2016-July/008727.html
[b] http://mail.openjdk.java.net/pipermail/jigsaw-dev/2015-December/005745.html
[c] http://mail.openjdk.java.net/pipermail/jpms-spec-experts/2016-September/000391.html
More information about the jpms-spec-experts
mailing list