说实话,我们总是用人家 JDK 的内部 API,是不是有点儿欺负人。
- Java 17 更新(1):更快的 LTS 节奏
- Java 17 更新(2):没什么存在感的 strictfp, 这回算是回光返照了
- Java 17 更新(3):随机数生成器来了一波稳稳的增强
- Java 17 更新(4):这波更新,居然利好 mac 用户
- Java 17 更新(5):历史包袱有点儿大,JDK 也在删代码啦
今天我们来聊聊 JEP 403: Strongly Encapsulate JDK Internals。这一条对于使用 JDK 内部 API 的应用场景来讲会比较受影响。
JDK 的动作还是很慢的,它给开发者提供了相当长的过渡期。从 Java 9 引入模块化开始,JDK 对于其内部的 API 的访问限制就已经明确开始落地,只是当时我们可以通过配置启动参数 --illegal-access 来继续使用 JDK 的内部 API,其中 Java 9 - Java 15 这个参数默认 permit,Java 16 默认 deny。
不过,现在不可以了。在 Java 17 当中使用 --illegal-access 将会得到以下警告,并且没有任何效果:
代码语言:javascript复制Java HotSpot(TM) 64-Bit Server VM warning: Ignoring option --illegal-access=permit; support was removed in 17.0
按照提案的说明,被严格限制的这些内部 API 包括:
- java.* 包下面的部分非 public 类、方法、属性,例如 Classloader 当中的 defineClass 等等。
- sun.* 下的所有类及其成员都是内部 API。
- 绝大多数 com.sun.* 、 jdk.* 、org.* 包下面的类及其成员也是内部 API。
举个例子:
代码语言:javascript复制package com.sun.beans;
...
public final class WeakCache<K, V> {
private final Map<K, Reference<V>> map = new WeakHashMap<K, Reference<V>>();
public V get(K key) { ... }
public void put(K key, V value) { ... }
...
}
在 java.desktop 模块下有这么一个类,非常简单,就是对 WeakHashMap 做了个包装。我想要用一下它,我该怎么办呢?
复制一份到我的工程里面。
不是,不是。。。优秀的程序员不应该 CV 代码。。。所以我直接使用它。
啊,不行。那我可以反射呀~ 我可真是个小机灵鬼。这波反射下来真是无人能敌。
代码语言:javascript复制try {
var weakCacheClass = Class.forName("com.sun.beans.WeakCache");
var weakCache = weakCacheClass.getDeclaredConstructor().newInstance();
var putMethod = weakCacheClass.getDeclaredMethod("put", Object.class, Object.class);
var getMethod = weakCacheClass.getDeclaredMethod("get", Object.class);
putMethod.invoke(weakCache, "name", "bennyhuo");
System.out.println(getMethod.invoke(weakCache, "name"));
} catch (Exception e) {
e.printStackTrace();
}
满怀欣喜的运行它。。。
代码语言:javascript复制java.lang.IllegalAccessException: class com.bennyhuo.java17.ReflectionsInternal cannot access class com.sun.beans.WeakCache (in module java.desktop) because module java.desktop does not export com.sun.beans to unnamed module @776ec8df
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:392)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:674)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:489)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
at com.bennyhuo.java17.ReflectionsInternal.useWeakCache(ReflectionsInternal.java:16)
at com.bennyhuo.java17.ReflectionsInternal.main(ReflectionsInternal.java:10)
en???这让我想起了 Android P,你看这个字母 P,它的发音充满了挑衅,它的形状还有点儿像官方在嘲笑我们
现在 Java 17 也玩这个啊,反射都不行了啊这。。
Java 16 我们可以通过在运行时加入 --illegal-access=permit
来运行,虽然会有一堆警告:
# java --illegal-access=permit com.bennyhuo.java17.ReflectionsInternal
Java HotSpot(TM) 64-Bit Server VM warning: Option --illegal-access is deprecated and will be removed in a future release.
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.bennyhuo.java17.ReflectionsInternal (file:/mnt/c/Users/benny/WorkSpace/Mario/SourceCode/Java17UpdatesDemo/src/) to constructor com.sun.beans.WeakCache()
WARNING: Please consider reporting this to the maintainers of com.bennyhuo.java17.ReflectionsInternal
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
bennyhuo
不过正如我们前面所说,Java 17 当中这个参数无效了:
代码语言:javascript复制# java --illegal-access=permit com.bennyhuo.java17.ReflectionsInternal
Java HotSpot(TM) 64-Bit Server VM warning: Ignoring option --illegal-access=permit; support was removed in 17.0
java.lang.IllegalAccessException: class com.bennyhuo.java17.ReflectionsInternal cannot access class com.sun.beans.WeakCache (in module java.desktop) because module java.desktop does not export com.sun.beans to unnamed module @372f7a8
d
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:392)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:674)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:489)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
at com.bennyhuo.java17.ReflectionsInternal.useWeakCache(ReflectionsInternal.java:16)
at com.bennyhuo.java17.ReflectionsInternal.main(ReflectionsInternal.java:10)
这就是上帝在关门的时候(Java 9),顺便也提醒我们窗户也马上要关上了,还不赶紧滚出去?然后上帝又花了三年把窗户也关上了(Java 17)。不过,它总算是还留了一个通气孔。。。
Java 17 当中 --add-opens 仍然有效,通过开启它可以让我们的程序在运行时通过反射访问指定的类:
代码语言:javascript复制--add-opens java.desktop/com.sun.beans=ALL-UNNAMED
所以,上面的代码想要运行,只能:
代码语言:javascript复制# java --add-opens java.desktop/com.sun.beans=ALL-UNNAMED com.bennyhuo.java17.ReflectionsInternal
bennyhuo
所以这波限制是要来真的,赶快跑吧!
大家也可以参考 受影响的 API 清单(https://cr.openjdk.java.net/~mr/jigsaw/jdk8-packages-strongly-encapsulated) 来规划自己的 JDK 升级。
顺便说一句,著名的 Unsafe 类不在这一波制裁的名单以内,可能是 Unsafe 应用太广泛了吧,而且 Java 官方也没有找到合适的替代品来满足需求,就先放着了(Unsafe 我们在后面访问堆外内存的内容中还会有介绍)。
好啦,关于加强控制内部 API 的限制的更新,我们也就介绍这么多,对大家的影响嘛,应该也不大(只要不升级)。
C 语言是所有程序员应当认真掌握的基础语言,不管你是 Java 还是 Python 开发者,欢迎大家关注我的新课 《C 语言系统精讲》: