1. Overview
1.概述
Before Java 9, the Java Reflection API has a superpower: It could gain access to the non-public class members without limitation. After Java 9, the modular system wants to limit the Reflection API to a reasonable extent.
在 Java 9 之前,Java Reflection API 有一个超级能力:它可以不受限制地获得对非公共类成员的访问。在Java 9之后,模块化系统希望将Reflection API限制在一个合理的范围内。
In this tutorial, we’ll inspect the relationship between the module system and reflection.
在本教程中,我们将检查模块系统和反射之间的关系。
2. Modular System and Reflection
2.模块化系统和反思
Even though reflection and the module system make their appearance at different times in Java’s history, they need to work together to build a reliable platform.
尽管反射和模块系统是在Java历史上的不同时期出现的,但它们需要一起工作以建立一个可靠的平台。
2.1. The Underlying Model
2.1.基础模型
One of the goals of the Java module system is strong encapsulation. The strong encapsulation mainly consists of readability and accessibility:
Java模块系统的目标之一是强封装。强封装主要包括可读性和可访问性。
- The readability of modules is a coarse concept and concerns whether one module has a dependency on another module.
- The accessibility of modules is a finer concept and cares if one class can access another class’s field or method. It is provided by class boundary, package boundary, and module boundary.
The relationship between these two rules is that readability comes first, and accessibility builds upon readability. For example, if a class is public but not exported, the readability will prevent further use. And, if a non-public class is in an exported package, the readability will allow its passing, but the accessibility will reject it.
这两条规则之间的关系是:可读性是第一位的,而可及性则建立在可读性之上。例如,如果一个类是public但没有导出,可读性将阻止进一步的使用。而且,如果一个非公共的类在一个导出的包中,可读性将允许它通过,但可访问性将拒绝它。
To increase the readability, we can use the “requires” directive in the module declaration, specify the “–add-reads” option on the command line, or invoke the Module.addReads method. In the same way, to break the boundaries encapsulation, we can use the “opens” directive in the module declaration, specify the “–add-opens” option on the command line, or invoke the Module.addOpens method.
为了提高可读性,我们可以在模块声明中使用”requires“指令,在命令行中指定”-add-reads“选项,或者调用Module.addReads方法。同样,为了打破边界封装,我们可以在模块声明中使用”opens“指令,在命令行中指定”-add-opens“选项,或者调用Module.addOpens方法。
Even reflection can’t break the readability and accessibility rules; otherwise, it will lead to corresponding errors or warnings. One thing to note: When using reflection, the runtime will automatically set up a readability edge between two modules. That also implies that if something goes wrong, it’s because of accessibility.
即使是反射也不能破坏可读性和可访问性规则;否则,会导致相应的错误或警告。有一点需要注意:当使用反射时,运行时将自动在两个模块之间设置可读性边缘。这也意味着,如果出了问题,那是因为可及性问题。
2.2. Different Reflection Use Cases
2.2.不同的反思用例
In the Java module system, there are different module types, for example, named module, unnamed module, platform/system module, application module, and so on:
在Java模块系统中,有不同的模块类型,例如,命名模块、未命名模块、平台/系统模块、应用模块等等。
To be clear, the two concepts “module system” and “system module” may sound confusing. So, let’s use the “platform module” concept instead of the “system module”.
说白了,”模块系统 “和 “系统模块 “这两个概念听起来可能很混乱。因此,让我们用 “平台模块 “的概念来代替 “系统模块”。
Considering the above module types, there exist quite a few combinations between different module types. Generally, an unnamed module can’t be read by named modules except for automatic modules. Let’s only inspect three typical scenarios where illegal reflective access happens:
考虑到以上的模块类型,不同的模块类型之间存在着相当多的组合。一般来说,除了自动模块,未命名的模块不能被命名的模块读取。我们只检查一下发生非法反射访问的三种典型情况。
In the above picture, deep reflection means using the Reflection API to get access to non-public members of a class by invoking the setAccessible(flag) method. When using reflection to access a named module from another named module, we’ll get an IllegalAccessException or InaccessibleObjectException. Similarly, when using reflection to access an application named module from an unnamed module, we get the same errors.
在上图中,深度反射意味着使用反射API,通过调用setAccessible(flag)方法来获得对一个类的非公共成员的访问。当使用反射从另一个命名模块访问一个命名模块时,我们会得到一个IllegalAccessException或InaccessibleObjectException。同样地,当使用反射从一个未命名的模块访问一个应用程序的命名模块时,我们会得到同样的错误。
However, when using reflection to access the platform module from an unnamed module, we’ll get an IllegalAccessException or a warning. And the warning message is useful to help us to find where the problem happens and to make further remedies:
然而,当使用反射从一个未命名的模块访问平台模块时,我们会得到一个IllegalAccessException或者一个警告。而这个警告信息很有用,可以帮助我们找到问题发生的地方,并做出进一步的补救措施。
WARNING: Illegal reflective access by $PERPETRATOR to $VICTIM
In the above warning message form, the $PERPETRATOR represents the reflecting class information and the $VICTIM represents the reflected class information. And, this message is attributed to the relaxed strong encapsulation.
在上面的警告消息形式中,$PERPETRATOR代表反映类信息,$VICTIM代表反映类信息。而且,这条消息是归于宽松的强封装。
2.3. Relaxed Strong Encapsulation
2.3.宽松的强封装
Before Java 9, many third-party libraries utilize the reflection API to do their magic work. However, the strong encapsulation rules of the module system would invalidate most of that code, especially those using deep reflections to access JDK internal APIs. That would be undesirable. For a smooth migration from Java 8 to Java 9’s modular system, a compromise is made: relaxed strong encapsulation.
在Java 9之前,许多第三方库利用反射API来完成其神奇的工作。然而,模块系统的强封装规则会使大部分的代码失效,特别是那些使用深度反射访问JDK内部API的代码。这将是不可取的。为了顺利地从 Java 8 迁移到 Java 9 的模块化系统,我们做出了一个妥协:放松强封装。
The relaxed strong encapsulation provides a launcher option –illegal-access to control the runtime behavior. We should note that the –illegal-access option only works when we use reflection to access platform modules from unnamed modules. Otherwise, this option has no effect.
放松的强封装提供了一个启动器选项-illegal-access来控制运行时行为。我们应该注意,-illegal-access选项只有在我们使用反射从未命名的模块访问平台模块时才有效。否则,这个选项没有任何作用。
The –illegal-access option has four concrete values:
-illegal-access选项有四个具体值。
- permit: opens each package of platform modules to unnamed modules and shows a warning message only once
- warn: is identical to “permit“, but shows a warning message per illegal reflective access operation
- debug: is identical to “warn“, and also prints the corresponding stack trace
- deny: disables all illegal reflective access operations
From Java 9, the –illegal-access=permit is the default mode. To use other modes, we can specify this option on the command line:
从Java 9开始,-illegal-access=permit是默认模式。要使用其他模式,我们可以在命令行中指定这个选项。
java --illegal-access=deny com.baeldung.module.unnamed.Main
In Java 16, the –illegal-access=deny becomes the default mode. Since Java 17, the –illegal-access option is entirely removed.
在 Java 16 中,-illegal-access=deny 成为默认模式。自Java 17以来,-illegal-access选项被完全删除。
3. How to Fix Reflection Illegal Access
3.如何修复反思非法访问
In the Java module system, a package needs to be open to allow deep reflection.
在Java模块系统中,包需要是开放的,以便进行深度反思。。
3.1. In Module Declaration
3.1.在模块声明中
If we’re the code author, we can open the package in the module-info.java:
如果我们是代码作者,我们可以在module-info.java中打开这个包。
module baeldung.reflected {
opens com.baeldung.reflected.opened;
}
To be more cautious, we can use the qualified opens:
为了更谨慎起见,我们可以使用限定的opens。
module baeldung.reflected {
opens com.baeldung.reflected.internal to baeldung.intermedium;
}
When migrating our existing code to the modular system, for convenience, we can open the whole module:
当把我们现有的代码迁移到模块化系统时,为了方便,我们可以打开整个模块。
open module baeldung.reflected {
// don't use opens directive
}
We should note that an open module doesn’t allow inner opens directives.
我们应该注意,一个开放的模块不允许内部opens指令。
3.2. On the Command-Line
3.2.在命令行上
If we’re not the code author, we can use the –add-opens option on the command line:
如果我们不是代码作者,我们可以在命令行上使用-add-opens选项。
--add-opens java.base/java.lang=baeldung.reflecting.named
And, to add opens to all unnamed modules, we can use the ALL-UNNAMED:
而且,为了向所有未命名的模块添加开放,我们可以使用ALL-UNNAMED。
java --add-opens java.base/java.lang=ALL-UNNAMED
3.3. At Runtime
3.3.在运行时
To add opens at runtime, we can use the Module.addOpens method:
要在运行时添加开放,我们可以使用Module.addOpens方法。
srcModule.addOpens("com.baeldung.reflected.internal", targetModule);
In the above code snippet, the srcModule opens the “com.baeldung.reflected.internal” package to the targetModule.
在上面的代码片段中,srcModule打开”com.baeldung.reflected.internal“包到targetModule。
One thing to note: the Module.addOpens method is caller-sensitive. This method will succeed only when we call it from the module being modified, from the modules it has granted open access to, or from the unnamed module. Otherwise, it will lead to an IllegalCallerException.
有一点需要注意:Module.addOpens方法是对调用者敏感的。只有当我们从被修改的模块、从它授予开放权限的模块、或从未命名的模块中调用该方法时,才会成功。否则,它将导致一个IllegalCallerException。
Another way to add opens to the target module is using the Java agent. In the java.instrument module, the Instrumentation class has added a new redefineModule method since Java 9. This method can be used to add extra reads, exports, opens, uses, and provides:
另一种向目标模块添加打开的方法是使用Java代理。在java.instrumentation模块中,Instrumentation类从Java 9开始增加了一个新的redefineModule方法。 这个方法可以用来增加额外的读取、导出、打开、使用和提供。
void redefineModule(Instrumentation inst, Module src, Module target) {
// prepare extra reads
Set<Module> extraReads = Collections.singleton(target);
// prepare extra exports
Set<String> packages = src.getPackages();
Map<String, Set<Module>> extraExports = new HashMap<>();
for (String pkg : packages) {
extraExports.put(pkg, extraReads);
}
// prepare extra opens
Map<String, Set<Module>> extraOpens = new HashMap<>();
for (String pkg : packages) {
extraOpens.put(pkg, extraReads);
}
// prepare extra uses
Set<Class<?>> extraUses = Collections.emptySet();
// prepare extra provides
Map<Class<?>, List<Class<?>>> extraProvides = Collections.emptyMap();
// redefine module
inst.redefineModule(src, extraReads, extraExports, extraOpens, extraUses, extraProvides);
}
In the above code, we first utilize the target module to construct the extraReads, extraExports, and extraOpens variables. Then, we invoke the Instrumentation.redefineModule method. As a result, the src module will be accessible to the target module.
在上述代码中,我们首先利用target模块来构建extraReads、extraExports和extraOpens变量。然后,我们调用Instrumentation.redefineModule方法。结果,src模块将被target模块访问。
4. Conclusion
4.总结
In this tutorial, we first introduced the readability and accessibility of the module system. Then, we looked at different illegal reflective access use cases and how relaxed strong encapsulation helps us to migrate from Java 8 to the Java 9 module system. Finally, we provided different approaches to solve the illegal reflective access.
在本教程中,我们首先介绍了模块系统的可读性和可访问性。然后,我们研究了不同的非法反射访问用例,以及放松的强封装如何帮助我们从Java 8迁移到Java 9的模块系统。最后,我们提供了不同的方法来解决非法反射访问的问题。
As usual, the source code for this tutorial can be found over on GitHub.
像往常一样,本教程的源代码可以在GitHub上找到超过。