问题
反射调用内部API时会报错
Exception in thread "main" java.lang.IllegalAccessException: class cn.lingex.Main (in module maven.lib.test) cannot access class jdk.internal.misc.Unsafe (in module java.base) because module java.base does not export jdk.internal.misc to module maven.lib.test
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:398)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:709)
at java.base/java.lang.reflect.Method.invoke(Method.java:571)
at maven.lib.test/cn.lingex.Main.main(Main.java:42)
原因是java9后增加了模块化检查,此时需要setAccessible关闭检查。
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make public static jdk.internal.misc.Unsafe jdk.internal.misc.Unsafe.getUnsafe() accessible: module java.base does not "exports jdk.internal.misc" to module maven.lib.test
at java.base/java.lang.reflect.AccessibleObject.throwInaccessibleObjectException(AccessibleObject.java:388)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:364)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:312)
at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:203)
at java.base/java.lang.reflect.Method.setAccessible(Method.java:197)
调用setAccessible也会报错!大致意思是模块java.base中的jdk.internal.misc包没有导出到我自己的模块,也有检查。
虽然使用启动参数能够解决,但是不够优雅,如果你提供依赖给别人让别人加启动参数才能用。所以我们需要通过代码解决
解决
- debug调试下发现在Method做了最终的校验checkCanSetAccessible(),校验有很多,我们找能够操作的地方
如图,在这个校验中使用了调用所在类的模块与方法所在类的模块进行了对比,若相等则返回true,我们从这里入手。将调用时所在的类模块替换成方法所在类模块
public static void main(String[] args) throws Exception {
Class<?> unsafeClass = Class.forName("jdk.internal.misc.Unsafe");
Method method = unsafeClass.getMethod("getUnsafe");
// 获取Unsafe所在模块
Module unsafeModule = unsafeClass.getModule();
// 获取当前模块
Module currentModule = Main.class.getModule();
// 替换Main.class所在模块
UnsafeObjectModify.setObjectFieldValueRef(Main.class,"module",unsafeModule);
method.setAccessible(true);
// 设置完成后替换回来
UnsafeObjectModify.setObjectFieldValueRef(Main.class,"module",currentModule);
// 获取到unsafe对象
Object unsafe = method.invoke(unsafeClass);
System.out.println(unsafe); // jdk.internal.misc.Unsafe@3eb07fd3
}
- 另一种方法:查看setAccessible最后做了什么
它调用了父类AccessibleObject的setAccessible0方法,将成员override设置成了我们给的值,我们更改下代码
public static void main(String[] args) throws Exception {
Class<?> unsafeClass = Class.forName("jdk.internal.misc.Unsafe");
Method method = unsafeClass.getMethod("getUnsafe");
// 将method的override成员设置为true(等同于无校验的method.setAccessible(true))
UnsafeObjectModify.setObjectFieldValueBool(method,"override",true);
Object unsafe = method.invoke(unsafeClass);
System.out.println(unsafe); // jdk.internal.misc.Unsafe@3eb07fd3
}
用到的依赖
<dependency>
<groupId>io.github.lngex</groupId>
<artifactId>object-unsafe</artifactId>
<version>1.0.1</version>
</dependency>