Proposal: #ReflectiveAccessToNonExportedTypes (revised) & #AwkwardStrongEncapsulation: Weak modules & private exports
Remi Forax
forax at univ-mlv.fr
Fri Sep 16 13:44:17 UTC 2016
Hi all,
the goals of this proposal is
- to explicitly mark packages that allow 'deep' reflection,
- disable 'deep' reflection by default even for exported packages
- ease the transition by adding a new migration state between automatic module and strong module (weak module).
so 3 goals with one stone :)
Have i miss something obvious but the last goal, easing migration is not listed in the issue summary ?
While adding a new kind of module to ease migration is maybe a good idea, it also add a new kind of modules when thinking about the modules and their relationship, which seems a terrible idea. We are loosing the KISS principle of the current module system.
I fully agree that 'deep' reflection should be disallowed by default and can be allowed package by package.
But as Stephen Colebourne, i fail to see why enabling 'deep' reflection on a package is related to the export keyword. While 'deep' reflection is somewhat related to visibility, it's not only about visibility of a class/method/field only, by example you can change the value of a public final field of a public class using setAccessible. In my opinion, exposing the API to 'deep' reflection is a different concern as exporting a package, thus using 'private' as a modifier on export is a bad idea (it also doesn't work well with the notion of restricted export (export to ...)).
Adding a new keyword 'expose' as Stephen propose to enable 'deep' reflection is a better way to answer to the goal 1 and 2.
About the other proposed items:
- using transitive instead of public for require, i fully agree.
- about removing the dynamic exports feature, a package declared as non exported but declared as exposed will have the same meaning, no ? if yes, i agree that the notion dynamic export should be removed.
regards,
Rémi
----- Mail original -----
> De: "Mark Reinhold" <mark.reinhold at oracle.com>
> À: jpms-spec-experts at openjdk.java.net
> Envoyé: Lundi 12 Septembre 2016 17:08:01
> Objet: Proposal: #ReflectiveAccessToNonExportedTypes (revised) & #AwkwardStrongEncapsulation: Weak modules & private
> exports
> 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
> --------
>
> (Warning: This is somewhat long, and in the end it affects both `exports`
> and `requires` directives.)
>
> Extend the language of module declarations with the concept of _weak
> modules_. Weak modules make it easy to modularize components whose
> internals will be accessed by reflection-based frameworks. Every
> package in a weak module has the following properties:
>
> (A) It is exported at both compile time and run time, as if by an
> `exports` directive, and
>
> (B) Its non-public elements are available for _deep_ reflection, i.e.,
> at run time they can be made accessible to code outside the module
> via the `AccessibleObject::setAccessible` method of the core
> reflection API.
>
> In other words, every type defined in a weak module, whether public or
> not, is subject to exactly the same access checks as in Java SE 8 and
> earlier releases.
>
> A weak module is declared by placing the modifier `weak` before the
> `module` keyword. The declaration of a weak module cannot contain any
> explicit `exports` directives. If the `weak` modifier does not appear
> before the `module` keyword then the declared module is _strong_, and
> it can contain explicit `exports` directives.
>
> Suppose we have a module `foo.bar` which has an internal package
> `com.foo.bar.model` that contains entity classes to be manipulated by
> Hibernate, via core reflection. Then the module declaration
>
> weak module foo.bar {
> // No exports
> requires hibernate.core;
> requires hibernate.entitymanager;
> }
>
> exports the public types in `com.foo.bar.model`, and those of any other
> packages, in all phases. It additionally makes all non-public elements
> of all packages available for deep reflection, enabling Hibernate to
> access such elements in the `com.foo.bar.model` package via the
> `setAccessible` method.
>
> Weak modules simplify the process of migrating to modules. The steps
> to convert an existing component into a module were, previously:
>
> (1) Make any changes necessary to get it working as an automatic
> module (e.g., eliminate duplicate packages), and then
>
> (2) Write an explicit module declaration, which entails identifying
> both the component's dependences (`requires`) and the packages
> whose public types are to be made available to other modules
> (`exports`).
>
> With weak modules we can now divide the second step into two steps:
>
> (2a) Write an explicit module declaration for a weak module, which
> entails identifying just the component's dependences (`requires`).
>
> (2b) Convert the weak module into a strong module, which entails
> identifying the packages of the component whose public types
> are to be made available to other modules (`exports`).
>
> In other words, weak modules make it possible to focus first upon the
> reliable configuration of a module (`requires`), and then later think
> about its strong encapsulation (`exports`).
>
> Weak modules are "weak" in what they export, but they remain subject
> to all of the constraints required to achieve reliable configuration.
> They do not read the unnamed module (i.e., the class path), they do not
> allow cycles in the module graph, and they do not allow split packages.
> Weak modules read named modules only as indicated by their `requires`
> directives, and they consume and provide services only as indicated by
> their `uses` and `provides` directives.
>
> * * *
>
> In a strong module, an ordinary `exports` directive exports a package at
> both compile time and run time (property (A) above) but does not make its
> non-public types available for deep reflection (B). In order to enable a
> package in a strong module to be exported in the same way as in a weak
> module we introduce the per-export modifier `private` to denote this
> second property.
>
> If the above weak `foo.bar` module, e.g., contains some other packages
> besides `com.foo.bar.model`, and we wish to encapsulate those packages,
> we can convert it into a strong module with the declaration
>
> module foo.bar {
> exports private com.foo.bar.model;
> requires hibernate.core;
> requires hibernate.entitymanager;
> }
>
> Now Hibernate can still access any public or non-public entity classes in
> the `com.foo.bar.model` package, but all the other packages are strongly
> encapsulated.
>
> The `private` modifier should generally not be used to export a package
> containing an API, since normally an API's internal implementation
> details should be strongly encapsulated. It may, however, be useful for
> legacy APIs whose internals are known to be accessed by existing code.
>
> Every package in a weak module, an automatic module, or an unnamed module
> is exported as if by an `exports private` directive.
>
> To ensure the integrity of the platform we expect few, if any, packages
> in the JDK itself to be exported with the `private` modifier.
>
> * * *
>
> The new `private` modifier can also be used with qualified exports,
> though they interact with unqualified exports in a non-obvious way.
>
> - If you write `exports p` then you can also write `exports private
> p to m`, so that code in module `m` can access the non-public types
> of `p` via deep reflection but code outside of `m` can only access
> the public types of `p`.
>
> - If you write `exports p to m1` then you can also write `exports
> private p to m2`, so that code in `m2` can access the non-public
> types of `p` via deep reflection, code in `m1` can access the
> public types of `p`, but no code in any other module can access
> any of the types of `p`.
>
> - If you write `exports private p` then you cannot also have a
> qualified export of `p`, since code in all other modules already
> has access to the non-public types of `p` via deep reflection.
>
> Put informally, you can give your friends additional access, but you
> can't discriminate against them by giving them less access than everyone
> else.
>
> As before, duplicate `exports` directives are not permitted, in order to
> ensure easy readability. At most one `exports` directive is relevant to
> any given package/module pair, and it's easy to determine which one.
>
> * * *
>
> The introduction of `private` as a modifier of `exports` directives calls
> the existing syntax of `requires public` even more strongly into question
> than before. A module declaration of the form
>
> module foo.bar {
> exports private com.foo.bar.baz;
> requires public java.sql;
> }
>
> is likely to be very confusing to an uninformed reader. The `private`
> modifier in the `exports` directive means that the private elements of
> the `com.foo.bar.baz` package are exported for deep reflection at run
> time. The `public` modifier in the `requires` directive, however, does
> not mean that the public elements of the `java.sql` module are needed by
> this module; that is true of any plain `requires` directive. It means
> that, additionally, any client of this module is granted implied
> readability to the `java.sql` module, thereby gaining access to all of
> its exported types.
>
> To reduce this confusion we rename the `public` modifier in `requires`
> directives to `transitive`. Thus the above example becomes
>
> module foo.bar {
> exports private com.foo.bar.baz;
> 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.
>
>
> Notes
> -----
>
> - This is significantly different from the first proposal [3]. It adds
> the notion of weak modules, to ease migration, and also the notion of
> exporting a package without enabling deep reflection, to strengthen
> encapsulation.
>
> - This proposal removes the notion of dynamic exports, which in the
> presence of private exports would introduce considerable complexity
> into the interactions between qualified and unqualified exports.
> This means that it is no longer possible to export a package only at
> run time, so it is no longer possible for the author of a module to
> express the intent that the types of a non-API package are meant to
> be available to frameworks for deep reflection at run time but
> inaccessible at compile time. The dynamic-export feature could, if
> needed, be added in a future release.
>
> - A strong module with no exports makes no types accessible to code in
> other modules while a weak module makes all of its types accessible,
> both directly and via deep reflection. The declarations of such
> modules are, however, visually similar since most of their text lies
> between the curly braces:
>
> module m1 {
> requires ...;
> uses ...;
> provides ...;
> }
>
> weak module m2 {
> requires ...;
> uses ...;
> provides ...;
> }
>
> We suspect that this visual similarity will not cause much confusion
> in practice since strong modules that export no packages will be very
> rare.
>
> - If a container is to ensure that a package in an application module
> is available for deep reflection only by a trusted framework then it
> can arrange for that by rewriting that module's descriptor, as
> suggested previously [4], to insert the appropriate qualified private
> export. If there is a possibility that two modules will need to
> export a package of the same name to the same framework module, as
> suggested by Jason Greene [5], then the container should instead
> inject a small class into each module whose static initializer
> invokes the `Module::addExports` method in order to export the
> package to the framework module. There is no need any longer for
> the resolution algorithm to take this scenario into account [6].
>
> - 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 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 the `--add-exports` command-line option, the legacy
> unsupported `sun.misc.Unsafe` API and related APIs, or JVM TI.
>
> - Using the `--add-exports` option or its equivalent remains awkward,
> and sometimes it's 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 [7]. This is tracked as #AddExportsInManifest [8].
>
>
> [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-June/000307.html
> [4] http://mail.openjdk.java.net/pipermail/jigsaw-dev/2016-July/008637.html
> [5] http://mail.openjdk.java.net/pipermail/jigsaw-dev/2016-July/008641.html
> [6] http://mail.openjdk.java.net/pipermail/jigsaw-dev/2016-July/008727.html
> [7] http://mail.openjdk.java.net/pipermail/jigsaw-dev/2015-December/005745.html
> [8] http://openjdk.java.net/projects/jigsaw/spec/issues/#AddExportsInManifest
More information about the jpms-spec-experts
mailing list