menu
Lxnet Alpha 项目
search
Lxns Network
home
主页
person
登录
how_to_reg
注册
高版本Java中的反射
MeiVinEight
发表于 2021-06-19 21:49:35
编辑于 2021-06-19 21:49:35
# **Java提供的两种反射** ### java.lang.reflect包 最简单的一种反射方式,但同时也是效率最低的一种 正如所说,这套工具是在运行时检查访问,因此可以通过调用`AccessibleObject#setAccessible(boolean)`来很容易的访问一些原本不能访问的成员 例如,反射调用`URLClassLoader#addURL(URL)`可以通过 ```java public static void addURL(URLClassLoader classLoader, URL url) { Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); method.setAccessible(true); method.invoke(classLoader, url); } ``` 理论上只要安全管理器允许,可以访问到任何成员 但如果在Java 9或更高的版本中使用这种方式会看见这样的警告 ```java WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by %类名% (file:/%类路径%) to method java.net.URLClassLoader.addURL(java.net.URL) WARNING: Please consider reporting this to the maintainers of %类名% 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 ``` 而Java 16开始默认拒绝非法访问,于是会直接报错 ```java Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make protected void java.net.URLClassLoader.addURL(java.net.URL) accessible: module java.base does not "opens java.net" to unnamed module @404b9385 at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:357) at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297) at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199) at java.base/java.lang.reflect.Method.setAccessible(Method.java:193) at org.mve.Main.main(Main.java:12) ``` ### java.lang.invoke包 了解这套工具的都知道,这是Java提供的一套非常高效的反射库,但仅通过`Lookup.find`只能访问ava中正常情况下可以合法访问的成员 比如,调用`String#concat(String)`可以通过 ```java MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle concat = lookup.findVirtual(String.class, "concat", MethodType.methodType(String.class, String.class)); String str = concat.invoke("AA", "BB"); // str is "AABB" ``` 如果要实现非法访问反射,需要`Lookup.unreflect`,但这任然需要传入的参数的反射访问是允许的,也就是任然需要调用`AccessibleObject#setAccessible(boolean)` 依然用`addURL`作为例子,使用这套工具看起来是这样子的 ```java MethodHandles.Lookup lookup = MethodHandles.lookup(); Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); method.setAccessible(true); MethodHandle addURL = lookup.unreflect(method); public static void addURL(URLClassLoader classLoader, URL url) { addRL.invoke(classLoader, url); } ``` 也许会有人问:既然用这套工具也需要依靠`AccessibleObject#setAccessible(boolean)`,那为什么不直接用java.lang.reflect里面的工具呢 很显然,这套工具最大的优势在于高效,有着和直接调用相等效率的高效 # Java 16中做到全反射 正如刚才所说,Java提供的两套工具都不能做到全反射而仅剩合法访问,那么要怎么才能做到完全反射呢 首先,要尝试使用`sun.misc.Unsafe` ```java Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafe.setAccessible(true); Unsafe unsafe = (Unsafe) theUnsafe.get(null); ``` 会发现这段代码既没有报错也没有访问警告,而以下提供的几点全反射思路都建立在`sun.misc.Unsafe`基础上 ### 更改调用setAccessible的类的模块 Java没有把`Class#module`字段加入反射过滤,这意味着可以获取到这个字段的Field,而利用Unsafe更改 在分析`AccessibleObject#setAccessible(boolean)`中访问检查部分的代码,可以看到有一个关键的检查 ```java if (callerModule == Object.class.getModule()) return true; ``` 这意味着如果调用者的模块是`java.base`则直接返回true表示可以访问 那么可以通过Unsafe来手动把调用者的模块改为`java.base` ```java Field module = Class.class.getDeclaredField("module"); long offset = unsafe.objectFieldOffset(module); unsafe.putObject(Main.class, offset, Object.class.getModule()); ``` > Main.class换成自己的类,这个类则是可以无报错无警告调用`AccessibleObject#setAccessible(boolean)`的类 有了这个之后,就可以调用`AccessibleObject#setAccessible(boolean)`甚至后来使用`Lookup.unreflect` 但仅限于在改为`java.base`模块的类中调用 ### 一个完整权限的Lookup 既然Lookup的unreflect方法可以做到非法访问而只是需要setAccessible,那么说明Lookup是有这个能力的,为什么它自己不行呢,答案任然是访问检查 在Lookup的访问检查中,可以找到一点关键突破点 ```java Class> caller = lookupClassOrNull(); if (caller != null && !VerifyAccess.isClassAccessible(refc, caller, allowedModes)) throw new MemberName(refc).makeAccessException("symbolic reference class is not accessible", this); ``` 仅当caller不为null时才会调用完整的权限检查,而在lookupClassOrNull方法中 ```java private Class> lookupClassOrNull() { return (allowedModes == TRUSTED) ? null : lookupClass; } ``` 任然有一个特权检查,当该Lookup对象的allowedModes字段为TRUSTED时会返回null,意味着不会调用反射检查而默认可访问任何成员 可以找到在Lookup类中有一个`IMPL_LOOKUP`字段 ```java static final Lookup IMPL_LOOKUP = new Lookup(Object.class, TRUSTED); ``` 在实例化时传入的第二个参数正是刚才的TRUSTED,那么就意味着通过这个Lookup可以访问任何成员 而这个字段没有加入反射过滤,任然可以利用Unsafe获取 ```java Field field = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP"); long offset = unsafe.staticFieldOffset(field); MethodHandles.Lookup lookup = (MethodHandles.Lookup) unsafe.getObject(MethodHandles.Lookup.class, offset); ``` 有了这里获取到的lookup即可完全反射 另外,在OpenJ9 VM中,获取到的`IMPL_LOOKUP`并非完整权限,需要对其中的`accessClass`做更改 在Java中有一个JVM提供特权的类,在Java 8中完整类名是`sun.reflect.MagicAccessorImpl`而在Java 9之后是`jdk.internal.reflect.MagicAccessorImpl` 在JVM中会对该类做特殊检查,而使得该类拥有一些特权,包括完整的反射权限 ```java int jvmVersion = ...; // Get jvm version String className; if (jvmVersion == 8) { className = "sun.reflect.MagicAccessorImpl"; } else { className = "jdk.internal.reflect.MagicAccessorImpl"; } Class> clazz = Class.forName(className); Field accessClass = MethodHandles.Lookup.class.getDeclaredField("accessClass"); long offset = unsafe.objectFieldOffset(accessClass); unsafe.putObject(lookup, clazz); ``` > putObject方法中lookup参数为用刚才的方法获取到的Lookup 本贴特别鸣谢: @海螺螺
分享该文章:
MeiVinEight
@MeiVinEight
archive
最新文章
高版本Java中的反射
2021-06-19 21:49:35
查看更多
评论
请
登录
或
注册
。
暂无评论
评论
请登录或注册。
暂无评论