Java的非法反射警告illegal reflective access operation
反射是一项相当强大的特性,不仅在各类框架中被广泛应用,即使是在日常开发中我们也隔三差五得要和它打交道。然而在JDK9中JDK对反射加上了一些限制,需要注意。
考虑有如下的代码:
import java.lang.reflect.Field;
import java.util.ArrayList;
public class TestReflect {
public static int getCapacity(ArrayList<?> l) throws Exception {
Field dataField = l.getClass().getDeclaredField("elementData");
dataField.setAccessible(true); // 即使设置了可访问也会触发警告
return ((Object[]) dataField.get(l)).length; // 注意这行
}
public static void main(String[] args) {
var arr = new ArrayList<Integer>(4);
try {
System.out.println("capacity:" + TestReflect.getCapacity(arr));
} catch (Exception e) {
e.printStackTrace();
}
}
}
这段代码的作用是读取ArrayList
的实际容量,由于JDK并没有为我们提供类似cap()
这样的公开接口,所以我们不得不使用反射来绕过限制。
在JDK8上这段代码运行良好,然而当我们升级成JDK9后却会是这样的一幅画面:
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by TestReflect (file:/tmp/TestReflect.java) to field java.util.ArrayList.elementData
WARNING: Please consider reporting this to the maintainers of TestReflect
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
capacity:4
别紧张,代码还是正常运行了。其实这是JDK9中添加的新特性,即reflect不再可以访问non-public成员以及不可公开访问的class,原先这些访问控制虽然存在但是可以通过reflect绕过,从JDK9开始反射也将遵循访问控制的规则。JDK9中对于第一次访问非公开成员的操作会显示警告信息,我们可以通过``选项进一步显示出有用的warning提示:
$ java --illegal-access=warn TestReflect.java
WARNING: Illegal reflective access by TestReflect (file:/tmp/TestReflect.java) to field java.util.ArrayList.elementData
capacity:4
将warn替换为debug可以指出非法访问发生在哪一行(通过观察打印的调用堆栈),而替换为deny则将会直接抛出java.lang.reflect.InaccessibleObjectException
异常:
$ java --illegal-access=deny TestReflect.java
java.lang.reflect.InaccessibleObjectException: Unable to make field transient java.lang.Object[] java.util.ArrayList.elementData accessible: module java.base does not "opens java.util" to unnamed module @72967906
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:349)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:289)
at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:174)
at java.base/java.lang.reflect.Field.setAccessible(Field.java:168)
at TestReflect.getCapacity(TestReflect.java:7)
at TestReflect.main(TestReflect.java:14)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at jdk.compiler/com.sun.tools.javac.launcher.Main.execute(Main.java:415)
at jdk.compiler/com.sun.tools.javac.launcher.Main.run(Main.java:192)
at jdk.compiler/com.sun.tools.javac.launcher.Main.main(Main.java:132)
对于后续的更新版本,JDK会将deny作为默认的行为,不过目前(14.0.2)默认行为依旧为permit(显示那些默认的warning信息)。
显而易见,新特性会导致如下的结果:
- 无法再通过反射修改/访问其他类型的私有成员;
- 无法再通过反射使用internal APIs
然而健康的代码并不应该依赖于非法的访问:
- 标记为非公开的成员本身就不希望被外部直接访问,访问应该通过public APIs进行;
- 无视访问控制会导致数据的修改变得不可控,从而产生意想不到的缺陷;
- 私有成员可能会随着开发/更新/修复/重构等活动而改变,过度依赖于访问其他class的私有成员会产生高耦合性的代码,使维护的负担成倍增加
当然,想要消除警告也很容易,虽然官方文档中不建议无视或消除这个警告:
$ java --add-opens java.base/java.util=ALL-UNNAMED TestReflect.java
capacity:4
--add-opens
选项将特定的module下的package公开给制定的package,或者使用特殊值ALL-UNAMED
代指所有匿名包(比如本例中的类),公开后我们就可以通过reflect来访问被公开包中class的成员而不抛出异常了(需要使用setAccessible(true)
)
这种trick实际上严重破坏了代码的封装,个人是极不推荐的;此外还可以选择将JDK版本锁定在8,当然这也是不得以才为之的办法,那么对于即想尝试新版本又不想被警告信息烦扰的话该怎么办呢,只能尝试下面几种办法了:
- 如果是第三方依赖导致的警告,升级对应的依赖或是联系该依赖的维护人员,请他们尽快修复问题。通常来说这类向后兼容的问题修复无需花费很多的时间。
- 如果是自己的代码,则考虑是否要对私有成员提供可访问的public API,是否将私有成员公有化。
- 如果使用了某些internal apis,那么你的代码应该重构以避免过度依赖这些api。
参考资料
https://blog.codefx.org/java/java-9-migration-guide/#Illegal-Access-To-Internal-APIs
http://mail.openjdk.java.net/pipermail/jigsaw-dev/2017-June/012841.html
Java的非法反射警告illegal reflective access operation的更多相关文章
- Illegal reflective access by org.apache.hadoop.security.authentication.util.KerberosUtil
在使用Java API操作HBase时抛出如下异常: Illegal reflective access by org.apache.hadoop.security.authentication.ut ...
- 解决JDK9以上的非法反射访问警告
1 问题描述 JDK9以上很多库都有这种非法反射访问的警告,比如protostuff: 解决方法两个: JDK降级 添加JVM参数 2 原因 降到JDK8能解决以上问题. 但是这不是本文的重点. 先说 ...
- java 反射 报错:Attempt to get java.lang.Integer field "..." with illegal data type conversion to int
类: Integer id; 反射时: Field f = User.class.getDeclaredField("id"); f.setAccessible(true); in ...
- 小白都能学会的Java注解与反射机制
前言 Java注解和反射是很基础的Java知识了,为何还要讲它呢?因为我在面试应聘者的过程中,发现不少面试者很少使用过注解和反射,甚至有人只能说出@Override这一个注解.我建议大家还是尽量能在开 ...
- Java ”框架 = 注解 + 反射 + 设计模式“ 之 注解详解
Java "框架 = 注解 + 反射 + 设计模式" 之 注解详解 每博一文案 刹那间我真想令时光停住,好让我回顾自己,回顾失去的年华,缅怀哪个穿一身短小的连衣裙 和瘦窄的短衫的小 ...
- Java中的反射和注解
前言 在Java中,反射机制和注解机制一直是一个很重要的概念,那么他们其中的原理是怎么样呢,我们不仅仅需要会使用,更要知其然而之所以然. 目录 反射机制 反射如何使用 注解定义 注解机制原理 注解如何 ...
- Java SE之反射技术[Class,Field](一)
一.什么是反射? 反射库(Reflection Library)提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵Java代码的程序.这项功能被大量地应用在JavaBeans中,它是Java组 ...
- 【Java基础】反射和注解
前言 在Java中,反射机制和注解机制一直是一个很重要的概念,那么他们其中的原理是怎么样呢,我们不仅仅需要会使用,更要知其然而之所以然. 目录 反射机制 反射如何使用 注解定义 注解机制原理 注解如何 ...
- java学习--Reflection反射机制
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意方法和属性:这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制. ...
- 【转载】java中的反射
主要介绍以下几方面内容 理解 Class 类 理解 Java 的类加载机制 学会使用 ClassLoader 进行类加载 理解反射的机制 掌握 Constructor.Method.Field 类的用 ...
随机推荐
- 8.3随记之Ubuntu 系统中修改 DNS 的两种办法和暗链
8.3 随记 在 Ubuntu 系统中修改 DNS 的两种办法 在终端进行修改编辑 sudo nano /etc/resolv.conf 进行修改文件 例如:nameserver 8.8.8.8, 保 ...
- Linux TTY/PTS
转载:https://segmentfault.com/a/1190000009082089 当我们在键盘上敲下一个字母的时候,到底是怎么发送到相应的进程的呢?我们通过ps.who等命令看到的类似tt ...
- Java——面向对象(static关键字开始)
一.static 可以修饰成员变量和成员方法 关键字特点: 随着类的加载而加载: 优先于对象存在: 被类的所有对象共享: 可以通过类名直接调用: 注意事项: 在静态方法中是没有this关键字的 静态的 ...
- JAVAweek7
本周学习[函数][数组] 什么是函数: 函数就是定义在类中的具有特定功能的一段独立小程序.函数也称为方法. 函数的格式: ·修饰符 返回值类型 函数名(参数类型 形式参数) { 执行语句: retur ...
- 如何实现图像搜索,文搜图,图搜图,CLIP+faiss向量数据库实现图像高效搜索
如何实现图像搜索,文搜图,图搜图,CLIP+faiss向量数据库实现图像高效搜索 这是AIGC的时代,各种GPT大模型生成文本,还有多模态图文并茂大模型, 以及stable diffusion和sta ...
- linux-ELK安装配置
前言: ELK 是三个开源项目的首字母缩写,这三个项目分别是:Elasticsearch.Logstash 和 Kibana. • Elasticsearch 是一个搜索和分析引擎. ...
- [ABC266Ex] Snuke Panic (2D)
Problem Statement Takahashi is trying to catch many Snuke. There are some pits in a two-dimensional ...
- MyBatis-Plus雪花算法实现源码解析
1. 雪花算法(Snowflake Algorithm) 雪花算法(Snowflake Algorithm)是一种用于生成唯一标识符(ID)的分布式算法.最初由 Twitter 公司开发,用于生成其内 ...
- pytest框架学习-标签@pytest.mark.
标签 自定义标签(区分大小写) 可以标记测试用例,对测试用例进行分组,有利于对测试用例进行筛选. 比如:给用例打标为API,代表接口自动化的用例,打标方法为@pytest.mark.API,打标后,需 ...
- Next.js 开发指南 初始篇 | Next.js CLI
基础篇.实战篇.源码篇.面试篇四大篇章带你系统掌握 Next.js! 前言 欢迎学习 Next.js!在学习具体的知识点之前,我们先来创建一个 Next.js 项目.创建了可运行的项目,才能在学习 ...