通过反射绕过 Java 模块化

Bypassing Java modularity via reflection

Java 模块系统是否应该阻止模块通过反射访问其他模块,而不声明适当的模块依赖关系?

例如,在编译这个 hello world Java 11 class 时,它从另一个模块调用 class,正如预期的那样,它不会编译,因为依赖于 java.xml 缺失:

module m1 {}

package p1;
public class C1 {
    public static void main(String[] args) throws Exception {
        System.out.println(javax.xml.XMLConstants.XML_NS_URI);
    }
}

将模块依赖项添加到 java.xml 后,它会按预期编译和运行。

然而,这个class:

module m1 {}

package p1;
public class C1 {
    public static void main(String[] args) throws Exception {
       System.out.println(Class.forName("javax.xml.XMLConstants").getField("XML_NS_URI").get(null));
    }
}

运行并打印结果,无需声明对 java.xml:

的模块依赖
java -version
openjdk version "11.0.2" 2019-01-15

java -p bin -m m1/p1.C1
http://www.w3.org/XML/1998/namespace

如此有效,我们能够绕过 Java 模块化。

这适用于任何模块到任何其他已解析的模块。

这怎么可能?模块系统应该防止这种情况吗?

Java 模块系统中有两种可读性:static readability and reflective readability。在模块系统的第一个版本中(在 Java 9 发布之前的开发过程中),这两种类型是相同的。因此,您的第二个示例确实会因错误而失败,因为从 m1java.xml 没有 requires 子句。后来修改了这个政策,放宽了反射可读性的规则,因为这种严格的政策并没有很好地适应许多严重依赖反射的框架(例如 Spring)。现在模块系统不强制反射的可读性边缘(但目标类型仍然必须导出并且定义模块必须在模块图中)。

5.2 Reflective readability

...

To make the provider class accessible to the framework we need to make the provider’s module readable by the framework’s module. We could mandate that every framework explicitly add the necessary readability edge to the module graph at run time, as in an earlier version of this document, but experience showed that approach to be cumbersome and a barrier to migration.

We therefore, instead, revise the reflection API simply to assume that any code that reflects upon some type is in a module that can read the module that defines that type. This enables the above example, and other code like it, to work without change. This approach does not weaken strong encapsulation: A public type must still be in an exported package in order to be accessed from outside its defining module, whether from compiled code or via reflection.