Proposal (revised^2): #ReflectiveAccessToNonExportedTypes & #AwkwardStrongEncapsulation: Open modules & open packages

Remi Forax forax at univ-mlv.fr
Thu Oct 27 17:04:56 UTC 2016


I really like this proposal.

Remi 

On October 27, 2016 5:53:36 PM GMT+02:00, mark.reinhold at oracle.com wrote:
>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

-- 
Sent from my Android device with K-9 Mail. Please excuse my brevity.


More information about the jpms-spec-experts mailing list