Varieties of reflective access

mark.reinhold at oracle.com mark.reinhold at oracle.com
Thu Oct 27 15:54:36 UTC 2016


This note categorizes the different kinds of inter-module access
operations relevant to #ReflectiveAccessToNonExportedTypes, analyzes the
possible combinations, and then uses those combinations to relate the
presented proposals to each other.

Categories of inter-module access
---------------------------------

Access operations from code in one module to types in another module can
be divided into three categories (for simplicity we ignore the details of
protected elements):

  - _Static access_ is ordinary access at compile time via language
    constructs, and at run time via constant-pool resolution.  (This may
    include access from bytecodes generated at run time.)  Only public
    elements in exported packages can be accessed.  An API package
    intended to be used by other modules should be exported for static
    access.

  - _Shallow reflective access_ is access at run time via the Core
    Reflection API (`java.lang.reflect`) without using `setAccessible`.
    Only public elements in exported packages can be accessed.

  - _Deep reflective access_ is access at run time via the Core
    Reflection API, using the `setAccessible` method to break into
    non-public elements.  Any element, whether public or not, in any
    package, whether exported or not, can be accessed.  Deep reflective
    access implies shallow reflective access.

A key difference between static access and either kind of reflective
access is that, for static access, the module system ensures reliable
configuration.  If the types in a package are accessible statically then
the post-resolution consistency checks are in full force, so there are no
duplicate or conflicting packages.  If only reflective access is provided
then the relevant readability relationships may have been established
only at run time, so there could be split packages and, also, cycles in
the run-time readability graph.

Access via constant-pool resolution at run time without reliable
configuration is possible, but only in the presence of class loaders that
do not delegate to each other in accordance with the readability graph
constructed by the resolver.  The class loaders provided by the module
system itself always delegate correctly.  If custom loaders provided by,
e.g., a container, do not delegate correctly then chaos is likely to
ensue -- and that's not something that the module system can prevent.

Method handles and method-handle lookup objects provide static access,
checked at the point of creation rather than the point of use.  As such,
the notions of shallow vs. deep reflective access don't appear to be
relevant.  In the long term we want to move toward a world in which
clients of frameworks use such lookup objects to grant the access to
internals that is needed by those frameworks.  Whether that's practical
remains to be seen; if it proves not to be so then the shallow vs. deep
dichotomy could be leveraged to govern the creation of the privileged
method handles needed by frameworks that move away from the more brittle
core reflection API.

Combinations & use cases
------------------------

Here are all the combinations of categories, along with some use cases.
A shorter form of this table will be used below to summarize the various
proposals.

    Static  Shallow    Deep    Use cases
      N        N        N      Fully-encapsulated internal packages
      N        N        Y      (N/A)
      N        Y        N      Partly-encapsulated framework clients
      N        Y        Y      Non-encapsulated framework clients
      Y        N        N      API that disallows all reflective access
      Y        N        Y      (N/A)
      Y        Y        N      API that allows shallow reflection
      Y        Y        Y      API that allows deep reflection (= Java SE 8)

Notes and observations:

  - Fully-encapsulated internal packages are fundamental, and are the
    default for packages that are not listed in a module declaration.
    This row ("N N N") will be omitted henceforth.

  - A partly-encapsulated framework client forbids static access and
    allows only shallow reflective access.  This could be used for a
    non-API package whose public POJOs will be manipulated by a framework
    via public methods and fields, while keeping its non-public elements
    strongly encapsulated.  This distinction seems not worth making in
    practice, since reflective frameworks generally override existing
    language-level access controls as needed.  This row ("N Y N") will
    be omitted henceforth.

  - A non-encapsulated framework client forbids static access and allows
    deep reflective access.  This could be used for a non-API package
    whose internals, including private methods and fields, will be
    manipulated by a framework.  This is another fundamental case.

  - The "N/A" rows are labeled as such because deep reflective access
    implies shallow reflective access.  These rows will be omitted
    henceforth.

  - The ability to define an API that disallows all reflective access
    is theoretically interesting but of questionable utility.  This row
    ("Y N N") will be omitted henceforth.

Omitting the aforementioned rows and compressing horizontally, where "S"
in the "Refl?" column means "shallow" and "D" means "deep":

    Stat  Refl?  Use
      N     D    Non-encapsulated framework clients
      Y     S    API that allows shallow reflection
      Y     D    API that allows deep reflection (= Java SE 8)

We now need to decide which combinations are truly useful, and figure out
how to express those combinations in module declarations.  There are many
tradeoffs to be made, the principal one being between expressive power
and approachability.

(1) SotMS, 2016/3/8 [1]
-----------------------

The SotMS document is out of date with respect to the proposals currently
being discussed, but it reflects what's been in the JDK 9 mainline EA
builds for a long time now.

    Stat  Refl?  Declaration
      N     D    N/A
      Y     S    N/A
      Y     D    `exports p`

(2) `exports dynamic`, 2016/6/28 [2]
------------------------------------

    Stat  Refl?  Declaration
      N     D    N/A
      Y     S    N/A
      Y     D    `exports p` OR `exports dynamic p`

This `exports dynamic` construct provides static access, and thus ensures
reliable configuration, but it is ignored at compile time.

(3) Weak modules, 2016/9/12 [3]
-------------------------------

    Stat  Refl?  Declaration
      N     D    N/A
      Y     S    `exports p`
      Y     D    `exports private p` OR `weak module { ... }`

A weak module cannot contain any `exports` directives, and is thus a
useful waypoint on the migration trail.

This proposal was an attempt to simplify (2), by removing `dynamic` and
making weak modules an explicit mechanism rather than a shorthand for
something more complex.  Several commentators have suggested that this is
not just simple but too simple, since a package can be exported for deep
reflection only if it is also exported for static access at compile time.

(4) Colebourne #1
-----------------

    Stat  Refl?  Declaration
      N     D    `exposes p`
      Y     S    `exports p`
      Y     D    `exports p` AND `exposes p`

Colebourne's first proposal [4], as amended by his later comment [5] so
that exported packages are available for shallow reflection.

This proposal allows both `*` wildcards and qualified forms of both the
`exports` and `exposes` directives.  If you use a wildcard with either
directive then you cannot use that directive in any other way.  Wildcards
cannot be used with qualified directives.

(5) Colebourne #2 [5]
---------------------

    Stat  Refl?  Declaration
      N     D    `exposed module { ... }` ONLY
      Y     S    `exports p` (only in non-weak modules)
      Y     D    `weak module { ... }` ONLY

Here Colebourne simplifies his first proposal by removing the ability to
export a single package for anything other than both static access and
shallow reflection.  This does not allow precise control of reflective
access via qualified exports to trusted framework modules.

(6) Open modules & open packages [6]
------------------------------------

    Stat  Refl?  Declaration
      N     D    `opens p` in a `module { ... }`, OR `open module { ... }`
      Y     S    `exports p` in a `module { ... }`
      Y     D    `exports p` AND `opens p`, in a `module { ... }`,
                  OR `exports p` in an `open module { ... }`

This proposal is similar to Colebourne #1 (5), and to various unpublished
proposals.  It uses `opens` instead of `exposes`, `open module { ... }`
rather than wildcards with `exposes`, and does not allow wildcards with
`exports`.


[1] http://openjdk.java.net/projects/jigsaw/spec/sotms/2016-03-08
[2] http://mail.openjdk.java.net/pipermail/jpms-spec-experts/2016-June/000307.html
[3] http://mail.openjdk.java.net/pipermail/jpms-spec-experts/2016-September/000390.html
[4] http://mail.openjdk.java.net/pipermail/jigsaw-dev/2016-September/009370.html
[5] http://mail.openjdk.java.net/pipermail/jigsaw-dev/2016-October/009638.html
[6] http://mail.openjdk.java.net/pipermail/jpms-spec-experts/2016-October/000430.html


More information about the jpms-spec-experts mailing list