Proposal: #ReflectiveAccessToNonExportedTypes: `exports dynamic`

Mark Reinhold mark.reinhold at
Tue Jun 28 21:18:15 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]


Extend the language of module declarations so that a package can be
declared to be exported at run time but not at compile time.  This is,
roughly, the dual of the `requires static` construct proposed for
#CompileTimeDependences, hence we propose to introduce a new modifier,
`dynamic`, for use on the `exports` directive.  It has the following

  - At compile time, `exports dynamic P` does not cause the package `P`
    to be exported, though it does require `P` to be a package defined
    in the module.

  - In phases after compile time, `exports dynamic P` behaves in exactly
    the same way as `exports P`.  It therefore takes part fully in
    resolution and configuration, and is subject to the same consistency
    constraints as normally-exported packages (e.g., no split packages).

Thus a module declaration of the form

    module {
        requires hibernate.core;
        requires hibernate.entitymanager;
        exports dynamic;

makes the types in the `` package accessible at run time
but not at compile time.  In combination with the earlier change to
enable #ReflectionWithoutReadability [2] this means that frameworks that
today use core reflection to manipulate user classes at run time are more
likely to work out-of-the-box, without change, as automatic modules.  If
the `` package in this example includes entity classes
to be managed by Hibernate then the framework will be able to access them
without further ado, but under normal circumstances an attempt to compile
code that refers directly to those classes will fail.

The `dynamic` modifier can be applied to both unqualified and qualified
`exports` directives, though the caveats on using qualified exports [3]
still apply.


  - To access a non-public member in a dynamically-exported package you
    must still invoke the appropriate `setAccessible` method, just as
    you do today to access non-public members in an exported package.

  - This proposal is similar to Rémi's suggestion to add a way to export
    a package only for reflection, still requiring that `setAccessible`
    be invoked [4].  This proposal differs in that it does not require
    `setAccessible` to be invoked to access public members in a
    dynamically-exported package.

  - 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 packages must be exported at
    run time for reflective access by frameworks.  This proposal is also
    convenient for frameworks that generate code at run time, since the
    constant pool of a generated class file can include static references
    to types in dynamically exported packages.

  - This proposal opens the door to a category of second-class APIs.  If
    a package is exported dynamically then you can still compile code
    that refers to types in the package, by using `-XaddExports` or its
    equivalent at compile time, and it will work as expected at run time.
    It thus may be useful to use `exports dynamic` for packages that
    contain legacy APIs whose use is strongly discouraged, e.g., those in
    the `jdk.unsupported` module of the reference implementation, thereby
    forcing anyone who wants to compile against them to go to the trouble
    of using `-XaddExports`.

  - Intrusive access to arbitrary packages of arbitrary modules by, e.g.,
    debugging tools, will still require the use of sharp knives such as
    the `-XaddExports` command-line option or its equivalent, or JVM TI.

  - Using the `-XaddExports` option or its equivalent remains awkward,
    and sometimes it's the only way out.  To ease migration I think 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 [5].  I'll create a separate issue for that.

  - New kinds of resolution failures are possible.  If module `A`
    requires `B`, and `B` requires `C`, then if `B` and `C` both declare
    some package `P` to be exported dynamically, then a split-package
    error will be reported.  This may surprise, since the packages are
    just internal implementation details that were exported dynamically
    so that some framework could access their types.  This is not,
    however, all that different from the kinds of resolution and
    layer-creation failures that are already possible due to package
    collisions.  It's unlikely to happen all that often in practice,
    and the fix is straightforward: Just use reverse-DNS or otherwise
    sufficiently-unique package names, as most people already do.  It
    is worth exploring whether javac can detect at least some of these
    kinds of collisions and issue appropriate warnings.


More information about the jpms-spec-experts mailing list