JDK17 反射获取私有变量的一种方案
根据StackOverflow老哥的帖子,自己参考Spring的ReflectionUtils封了两个函数。
原因大意为这个Issue说的,JDK加了一套过滤器机制,避免java.base模块下的类的(私有)变量和方法被外部使用和依赖。可是谁让我们是写Java的,主打一个喜欢反射。
另外,这个在启动后添加类似--add-opens功能的方案似乎对JDK17没有什么用。
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ReflectionUtils {
/**
* 缓存变量
*/
private static final Map<Class<?>, Field[]> declaredFieldsCache = new ConcurrentHashMap<>();
private static final Field[] EMPTY_FIELD_ARRAY = new Field[0];
private static final Method getDeclaredFields0 = initializeStaticFinalField();
private static Method initializeStaticFinalField() {
try {
Method method = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
method.setAccessible(true);
return method;
} catch (NoSuchMethodException e) {
throw new RuntimeException();
}
}
/**
* Copied From org.springframework.util.ReflectionUtils.
* 已知JDK 17不会向无名模块(Unnamed Module)开放反射权限
* 将项目改造为模块似乎也没有任何作用,反而可能导致第三方框架的问题
* 因此需结合JVM参数实现,例如给JVM启动增加 --add-opens java.base/java.lang=ALL-UNNAMED
* 意为将java.base模块的java.lang包的(反射)权限开放给所有的无名包
* 即使这样,直接用Class.getDeclaredFields也是无效的,需要曲线先获取Class的私有方法getDeclaredFields0或getDeclaredFields
* 然后再在特定的java.lang包下的类的类对象调用Class类的私有方法
* 这种方法的问题在于一次性必须获取全部字段,没有开放单个字段的获取方法,或者说JDK这一层没有考虑为其他模块开放该操作
*
* @param publicOnly 是否仅获取公共字段,可以直接看Class.getDeclaredFields0
*/
public static Field[] getDeclaredFieldsAfterJDK12(Class<?> clazz, boolean publicOnly) {
if (clazz == null) {
throw new NullPointerException();
}
Field[] result = declaredFieldsCache.get(clazz);
if (result == null) {
try {
result = (Field[]) getDeclaredFields0.invoke(clazz, publicOnly);
declaredFieldsCache.put(clazz, (result.length == 0 ? EMPTY_FIELD_ARRAY : result));
} catch (Throwable ex) {
throw new IllegalStateException("Failed to introspect Class [" + clazz.getName() +
"] from ClassLoader [" + clazz.getClassLoader() + "]", ex);
}
}
return result;
}
/**
* @param name 必须完全同名,name会因为有空格匹配不上,没有考虑做处理
* @param publicOnly 同上
*/
public static Field getDeclaredFieldAfterJDK12(Class<?> clazz, String name, boolean publicOnly) {
if (clazz == null || name == null || name.trim().isEmpty()) {
throw new NullPointerException();
}
Field result = null;
try {
Field[] fields = getDeclaredFieldsAfterJDK12(clazz, publicOnly);
for (Field field : fields) {
if (name.equals(field.getName())) {
result = field;
break;
}
}
} catch (Throwable ex) {
throw new IllegalStateException("Failed to introspect Class [" + clazz.getName() +
"] from ClassLoader [" + clazz.getClassLoader() + "]", ex);
}
return result;
}
}