最近使用 mybatis-plus 框架,在执行 sql 查询的时候控制台日志提示了该警告信息(an illegal reflective access operation has occurred),并且只有首次执行的时候才提示,同样的方法再次调用则不会提示,我用的 jdk11。下面转载一篇文章,解决了这个问题。
warning: an illegal reflective access operation has occurred warning: illegal reflective access by com.baomidou.mybatisplus.core.toolkit.setaccessibleaction (file:/c:/users/wangbo.gtcom/.m2/repository/com/baomidou/mybatis-plus-core/3.4.3.4/mybatis-plus-core-3.4.3.4.jar) to field java.lang.invoke.serializedlambda.capturingclass warning: please consider reporting this to the maintainers of com.baomidou.mybatisplus.core.toolkit.setaccessibleaction 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
反射是一项相当强大的特性,不仅在各类框架中被广泛应用,即使是在日常开发中我们也隔三差五得要和它打交道。然而在 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 中对于第一次访问非公开成员的操作会显示警告信息,我们可以通过使用--illegal-access=warn选项进一步显示出有用的 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。
到此这篇关于浅谈mybatis-plus 提示发生了非法的反射访问操作的文章就介绍到这了,更多相关mybatis-plus 非法的反射访问内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论