Historically reflection could be used to break into any code that ran in the same JVM. With Java 9 this is going to change. One of the two main goals of the new module system is strong encapsulation; giving modules a safe space into which no code can intrude. These two techniques are clearly at odds so how can this stand off be resolved? After considerable discussions it looks like the recent proposal of open modules would show a way out.
If you're all down with the module system and what reflection does, you can skip the following back story and jump right into the stand off.
Setting the Scene
Let me set the scene of how the module system implements strong encapsulation and how that clashes with reflection.
Module Crash Course
The Java Platform Module Saloon (JPMS) is introducing the concept of modules, which in the end are just regular JARs with a module descriptor. The descriptor is compiled from a module-info.java
file that defines a module's name, its dependencies on other modules, and the packages it makes available:
module some.module {
requires some.other.module;
requires yet.another.module;
exports some.module.package;
exports some.module.other.package;
}
In the context of encapsulation there are two points to take note of:
- Only public types, methods, and fields in exported packages are accessible.
- They are only accessible to modules that require the exporting module.
Here, "being accessible" means that code can be compiled against such elements and that the JVM will allow accessing them at run time. So if code in module a user depends on code in a module owner, all we need to do to make that work is have user require owner and have owner export the packages containing the required types:
module user {
requires owner;
}
module owner {
exports owner.api.package;
}
This is the common case and apart from making the dependencies and API explicit and known to the module system all works as we're used to.
So far everybody's having fun! Then, in comes Reflection... conversations halt mid-sentence, the piano player stops his tune.
Reflection
Before Java 9, reflection was allowed to break into any code. Aside from some pesky calls to setAccessible
every type, method, or field in any class could be made available, could be called, could be changed - hell, even final fields were not safe!
Integer two = 2;
Field value = Integer.class.getDeclaredField("value");
value.setAccessible(true);
value.set(two, 3);
if (1 + 1 != two)
System.out.println("Math died!");
This power drives all kinds of frameworks - starting with JPA providers like Hibernate, coming by testing libraries like JUnit and TestNG, to dependency injectors like Guice, and ending with obsessed class path scanners like Spring - which reflect over our application or test code to work their magic. On the other side we have libraries that need something from the JDK that it would rather not expose (did anybody say sun.misc.Unsafe
?). Here as well, reflection was the answer.
So this guy, being used to getting what he wants, now walks into the Module Saloon and the bartender has to tell him no, not this time.
The Stand Off
Inside the module system (let's drop the saloon, I think you got the joke) reflection could only ever access code in exported packages. Packages internal to a module were off limits and this already caused quite a ruckus. But it still allowed to use reflection to access everything else in an exported package, like package-visible classes or private fields and methods - this was called deep reflection. In September rules got even stricter! Now deep reflection was forbidden as well and reflection was no more powerful than the statically typed code we would write otherwise: Only public types, methods, and fields in exported packages were accessible.
All if this caused a lot of discussions of course - some heated, some amicable but all with the sense of utter importance.
Some (myself included) favor strong encapsulation, arguing that modules need a safe space in which they can organize their internals without the risk of other code easily depending on it. Examples I like to give are JUnit 4, where one big reason for the rewrite was that tools depended on implementation details, reflecting down to the level of private fields; and Unsafe
, whose pending removal put a lot of pressure on a lot of libraries.
Others argue that the flexibility provided by reflection not only enables great usability for the many frameworks relying on it, where annotating some entities and dropping hibernate.jar onto the class path suffices to make things work. It also gives freedom to library users, who can use their dependencies the way they want, which might not always be the way the maintainers intended to. Here, Unsafe
comes in as an example for the other side: Many libraries and frameworks that are now critical to the Java ecosystem were only feasible exactly because some hacks were possible without the JDK team's approval.
Even though I tend towards encapsulation, I see the other arguments' validity as well. So what to do? What choices to developers have besides encapsulating their internals and giving up on reflection?
Choice of Weapons
So let's say we are in a position where we need to make a module's internals available via reflection. Maybe to expose it to a library or framework module or maybe because we are that other module and want to break into the first one. In the rest of the article we'll explore all available choices, looking for answers to these questions:
- What privileges do we need to employ that approach?
- Who can access the internals?
- Can they be accessed at compile time as well?
For this exploration we will create two modules. One is called owner and contains a single class Owner
(in the package owner
) with one method per visibility that does nothing. The other, intruder, contains a class Intruder
that has no compile time dependency on Owner
but tries to call its methods via reflection. Its code comes down to this:
Class<?> owner = Class.forName("owner.Owner");
Method owned = owner.getDeclaredMethod(methodName);
owned.setAccessible(true);
owned.invoke(null);
The call to setAccessible
is the critical part here, it succeeds or fails depending on how we decide to create and execute our modules. In the end we get output as follows:
public: ✓ protected: ✗ default: ✗ private: ✗
(Here only the public method could be accessed.)
All the code I'm using here can be found in a GitHub repository, including Linux scripts that run it for you.
Continue reading %Reflection vs Encapsulation – Stand Off in the Java Module System%
by Nicolai Parlog via SitePoint
No comments:
Post a Comment