Jason -- thanks for your feedback on this topic.
To put what Alex wrote in a somewhat different way, I'd say that the
tension here is between explicit configuration (as one finds today in,
e.g., the Maven world) and implicit configuration (IoC). Both approaches
are important. The former is typical of standalone Java SE applications
while the latter is typical of Java EE applications, though the two
approaches are often intermixed.
What we have in the design today seems to support the explicit approach
pretty well, but we're still trying to figure out how best to support the
implicit approach.
If I understand correctly, your view of the present proposal is that:
(1) It induces too much boilerplate, requiring developers to write
`exports dynamic P` for every single package `P` that's subject
to reflection by a framework, and
(2) It weakens encapsulation too much, by making the types in such a
package available for reflection at run time by any module in the
system.
These observations lead to your suggestion to allow declarative module
boundaries to be overridden by "trusted" framework code. It's far from
clear how to define such a facility in a way that would still allow us to
achieve one of our primary goals, namely strong encapsulation, i.e., the
ability of the author of a module to declare which types are accessible
by other components, and which are not. I'd therefore like to explain
and explore how the above issues can be addressed with the present
design.
* * *
To point (1), we all know that the most common way for developers to
write Java code today is with a rich and powerful IDE. These tools
already have plenty of built-in cleverness for generating POJO classes,
deriving precise `import` directives, and ameliorating other kinds of
boilerplate. I don't think it would be at all a stretch for such tools
to generate precise `exports dynamic` directives on demand, based upon
the presence of IoC-style annotations, and maintain their consistency
over time. Just as precise `import` directives in class and interface
declarations document dependences upon specific types, so precise
`exports dynamic` directives in a module declaration would document
the exposition of specific types for reflection at run time.
If we think it likely that some modules will need to export dozens or
hundreds of packages, leading to extremely long module declarations, then
one possible refinement would be to allow a wildcard: `exports dynamic *`
would export all of a module's packages for reflection at run time. This
would likely be straightforward.
* * *
To point (2), if some packages in a user module need to be exported for
reflection at run time, and a container wishes to ensure that only select
"trusted" framework modules can access the types in those packages, then
that's already expressible today. We can also ensure that the set of
packages exported by a module is the same whether it's used standalone
on Java SE versus inside a container, which as you observe elsewhere in
this thread [1] could be problematic.
Suppose, e.g., we have an application module that's written against JPA,
rather than any specific JPA implementation, and exports the package
containing its entity classes for reflection at run time:
module com.foo.data {
requires java.persistence;
exports dynamic com.foo.data.model;
}
When used standalone, outside of a container, this module will export the
package containing its entity classes for reflection at run time. The
classes will be accessible to every other module, but from a security and
integrity standpoint we assume that whoever invokes the run-time system,
i.e., whoever provides the command-line arguments to the `java` launcher
or its equivalent, is trusted to ensure that no adversarial modules are
present.
When used inside a container, the container already has the power to
prevent an adversarial module from accessing the module's entity classes.
That's because we expect containers to load every application into a
unique layer [2], and a container can rewrite module descriptors when
configuring a layer. This is nothing to be ashamed of -- we fully expect
it to become a common practice.
If the container is set up to provide, e.g., Hibernate to this particular
application, then it could narrow the accessibility of the entity classes
by rewriting the above module declaration to refine the `exports dynamic`
directive:
module com.foo.data {
requires java.persistence;
exports dynamic com.foo.data.model
to hibernate.core, hibernate.entitymanager;
}
(This is one of the very few use cases for qualified dynamic exports.)
Whether standalone or in a container the same set of packages is exported
by the module; the only difference is that, inside the container, the
exports are qualified.
* * *
To sum up, for (1) I agree that unnecessary boilerplate is a bad thing.
Asking the author of a module to be explicit about which packages are
exported for reflection at run time, however, is of high value when
trying to understand how the module fits into a larger system. The cost
of such explicitness can, moreover, easily be reduced by the tools that
almost all Java developers already use.
For (2), I share your concern and I think it can be addressed within the
scope of the present design. At this point I don't see a strong need to
introduce a way to enable framework code to violate module boundaries
arbitrarily at run time, and I don't know how to do that without,
essentially, giving up on one of our primary goals.
We could make it easier to rewrite module descriptors, by providing an
API for that purpose rather than expecting container developers to use
libraries such as ASM, and perhaps that's worth doing, but it's a
different issue.
- Mark
[1] http://mail.openjdk.java.net/pipermail/jigsaw-dev/2016-July/008530.html
[2] http://openjdk.java.net/projects/jigsaw/spec/sotms/#layers