| title | Java反射原理以及使用 | |
|---|---|---|
| date | 2018-11-21 02:56:52 -0800 | |
| tags |
|
|
| categories |
|
💠
-
- 3.1. Inflation
-
- 4.1. AccessibleObject
- 4.2. Annotation
- 4.3. Class
- 4.4. Field
- 4.5. Method
- 4.6. Constructor
- 4.7. Modifier
💠 2026-06-28 18:20:23
Reflection is powerful, but should not be used indiscriminately.
If it is possible to perform an operation without using reflection, then it is preferable to avoid using it.
参考: java8--类加载机制与反射(java疯狂讲义3复习笔记) 参考: Java8替代传统反射动态获取成员变量值的一个示例
在运行时 反射使程序能够在运行时探知类的结构信息:构造器,方法,字段... 并且依赖这些结构信息完成相应的操作,比如创建对象,方法调用,字段赋值...
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
考虑到许多反射调用仅会执行一次,Java 虚拟机设置了一个阈值 15(可以通过 -Dsun.reflect.inflationThreshold= 来调整),当某个反射调用的调用次数在 15 之下时,采用本地实现;
当达到 15 时,便开始动态生成字节码,并将委派实现的委派对象切换至动态实现,这个过程称之为 Inflation。
反射代理类加载器的潜在内存使用问题 - 简书
Inflation 引起的 MetaSpace Full GC 问题排查 - 腾讯云开发者社区-腾讯云
Java 反射的实现类
The AccessibleObject class is the base class for Field, Method and Constructor objects. It provides the ability to flag a reflected object as suppressing default Java language access control checks when it is used.
AccessibleObject 类是 Field、Method 和 Constructor 对象的基类。它提供了将反射的对象标记为 具有在使用时禁止Java语言的
默认访问控制检查的能力。
对于公共成员、默认(包级别)访问成员、受保护成员和私有成员,在分别使用 Field、Method 或 Constructor 对象来设置或获得字段、调用方法,或者创建和初始化类的新实例的时候,会执行访问检查。
在反射对象中设置 accessible 标志允许具有足够特权的复杂应用程序(比如 Java Object Serialization 或其他持久性机制)以某种通常禁止使用的方式来操作对象。
将此对象的 accessible 标志设置为指示的布尔值。
true 表示反射的对象在使用时应该取消 Java 语言访问检查, 反之则要进行。
此标志不会告诉您java访问修饰符是否可以访问该字段,它会告诉您当前是否忽略这些修饰符。
实际上setAccessible是启用和禁用访问安全检查的开关,并不是为true就能访问,为false就不能访问,一般情况下,我们并不能对类的私有字段进行操作,利用反射也不例外,
但有的时候,例如要序列化的时候,我们又必须有能力去处理这些字段,这时候,我们就需要调用AccessibleObject上的setAccessible(true)方法来允许这种访问,
而由于反射类中的Field,Method和Constructor继承自AccessibleObject,因此,通过在这些类上调用setAccessible()方法,我们可以实现对这些字段的操作。
但有的时候这将会成为一个安全隐患,为此,我们可以启用java.security.manager来判断程序是否具有调用setAccessible()的权限。
默认情况下,
内核API和扩展目录的代码具有该权限,而类路径或通过URLClassLoader加载的应用程序不拥有此权限。
- 什么情况下才是 默认可访问的。什么情况下 true 也不能访问
- 在反射的语境下,“默认可访问”指的是:如果一个成员在普通编写 Java 代码时就能直接通过 . 操作符访问,那么反射时它就是默认可访问的,无需显式调用 setAccessible(true)。
- true 也不能访问:
- 跨模块(Module)的非法反射(现代 Java 的最大限制)
- 操作 final 字段的特殊限制: final static 会内联反射修改不会生效,Java 14+ 引入的 record(记录类)不可修改其final字段值
- 开启了严苛的安全管理器(旧版本或特定企业环境) 安全管理器在 Java 17 中已被标记为废弃
自 Java 9 引入模块化(Project Jigsaw)以及 Java 16+ 默认开启**强封装(Strong Encapsulation)**后,setAccessible(true) 的行为发生了本质改变:
- Jigsaw 模块隔离:如果目标类属于一个未对外部显式开放(
opens)的命名模块(如 JDK 内核模块java.base的内部私有 API),哪怕调用setAccessible(true)也会直接抛出InaccessibleObjectException强行拦截。 - 打破限制的后门(JVM 参数):如果在启动现代 Java 应用时,必须允许某些第三方库(如旧版 Spring/MyBatis)通过反射访问 JDK 内部类,必须在启动命令行中手动追加参数:
--add-opens java.base/java.lang=ALL-UNNAMED
- SecurityManager 的终结:笔记中提到的
java.security.manager机制在现代 Java 中已被正式废弃并逐步移除,现代 Java 统一通过模块路径(Module Path)和强封装来替代传统的安全检查。
The Modifier class provides static methods and constants to decode class and member access modifiers.
API: modifier
Java的访问权限信息是以2的N次幂也就是bitmap的方式进行存储 一共有12个常用修饰符 也就使用了12位来标记
// 公共的, 意义一致
PUBLIC = 0x00000001;
PRIVATE = 0x00000002;
PROTECTED = 0x00000004;
STATIC = 0x00000008;
FINAL = 0x00000010;
SYNCHRONIZED = 0x00000020;
VOLATILE = 0x00000040;
TRANSIENT = 0x00000080;
NATIVE = 0x00000100;
INTERFACE = 0x00000200;
ABSTRACT = 0x00000400;
STRICT = 0x00000800;
// 不公开, 意义依据方法或者属性而定, 最大到16位
BRIDGE = 0x00000040;
VARARGS = 0x00000080;
SYNTHETIC = 0x00001000;
ANNOTATION = 0x00002000;
ENUM = 0x00004000;
MANDATED = 0x00008000;- 判断属性是否被 final 修饰
Modifier.isFinal(field.getModifiers()) - 移除 final 修饰符
field.getModifiers() & ~Modifier.FINAL
Field field = ReflectTargetObject.class.getDeclaredField("staticFinalString");
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);具有的功能
- 在运行时获取任意一个类所具有的成员变量和方法以及泛型类型;
- 在运行时构造任意一个类的对象;
- 在运行时调用任意一个对象的方法;
- 生成动态代理。
泛型擦除的存在, 但是泛型如果被用来进行声明, 类上,字段上,方法参数和方法返回值上,这些属于类的结构信息其实是会被编译进Class文件中的;
而泛型如果被用来使用,常见的方法体中带泛型的局部变量,其类型信息不会被编译进Class文件中。
前者因为存在于Class文件中,所以运行时通过反射还是能够获得其类型信息的;
所有的反射操作的入口都是从Class对象开始的, 获取Class对象有多种方式
- 通过类加载器加载class文件
Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass("com.takumiCX.reflect.ClassTest");
- 通过静态方法 Class.forName() 获取,需要传入类的全限定名字符串作参数
Class<?> clazz = Class.forName("com.takumiCX.reflect.ClassTest");
- 通过 类.class 获得类的Class对象
Class<ClassTest> clazz = ClassTest.class;
除了获得的Class对象的泛型类型信息不一样外,还有一个不同点值得注意。只有 forName() 方式 在获得class对象的同时会引起类的初始化
使用newInstance()操作无参构造方法
使用Class类中的newInstance()方法进行实例化操作, 但该方法必须要求类有无参构造方法
使用Class类中的getConstructors()获取所有构造方法
Class<?> cls = Class.forName("first.Book");
Constructor<?>[] constructors = cls.getConstructors();使用Class类中的getConstructor获取指定参数类型的构造方法
Class<?> cls = Class.forName("first.Book");
Constructor constructor = cls.getConstructor(String.class, String.class);
// 实例化对象
Book book = (Book) constructor.newInstance("java", "123");
System.out.println(book);getDeclaredMethods() 获取类本身定义的所有方法, 不包含由继承获取到的方法
Class<?> cls = Class.forName("first.Book");
Method[] methods = cls.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method);
}获取指定的方法
// 传入方法名和参数类型
Method method = cls.getDeclaredMethod("setName", String.class);getMethods() 获取所有方法, 包含由继承获取到的方法, 但无法取得自身私有方法
Class<?> cls = Class.forName("first.Book");
Method[] methods = cls.getMethods();
for (Method method : methods) {
System.out.println(method);
}获取指定的方法
Method method = cls.getMethod("setName", String.class);调用方法
Class<?> cls = Class.forName("first.Book");
Object object = cls.newInstance();
Method method = cls.getMethod("setName", String.class);
// 调用方法
method.invoke(object, "hello");取得所有成员
Class<?> cls = Class.forName("first.Book");
Field[] fields = cls.getDeclaredFields();
for (Field field : fields) {
System.out.println(field);
}获取单个成员
Class<?> cls = Class.forName("first.Book");
Field field = cls.getDeclaredField("name");
System.out.println(field);取得所有成员, 包含由继承获取的成员, 但无法取得自身私有成员
Class<?> cls = Class.forName("first.Book");
Field[] fields = cls.getFields();
for (Field field : fields) {
System.out.println(field);
}set 和 get 属性的值
Class<?> cls = Class.forName("first.Book");
Object object = cls.newInstance();
Field field = cls.getDeclaredField("name");
// 取消Java安全性检查
field.setAccessible(true);
// 设置值
field.set(object, "你好");
System.out.println(field.get(object));获取类的注解
Class<?> cls = Class.forName("first.Book");
Annotation[] as = cls.getAnnotations();
for (Annotation a : as) {
System.out.println(a);
}获取指定的Annotation
Class<?> cls = Class.forName("first.Book");
GetItem annotation = cls.getAnnotation(Deprecated.class);
System.out.println(annotation.name());
System.out.println(annotation.value());正常情况下 final修饰的类,变量,方法, 表示不可继承,不可修改,不可重写(override), 但是使用反射能在一定程度上进行修改
被final修饰过的变量,只是说栈存储的地址不能再改变,但是却没有说地址指向的内容不能改变,所以反射可以破final,因为它修改该了以前地址的具体内容,但是没有改地址的信息。
参考 JavaDoc: Java8
Field.set()的文档说明
Spring 中的 IOC 主要是依据反射来实现的, 只在启动阶段性能有所损耗, 关注性能以及热点代码最好避免使用反射 例如常见的BeanCopy
从一起GC血案谈到反射原理总结: Method等Accessor对象每次get时会复制构造出新的对象,所以一般需要缓存; 反射数据是软引用
优化方案