Java Reflection:java反序列化基础详解

2025-04-24 2 0

反射机制

java反射是一种特性,反射机制允许在程序云时动态访问(查询,修改)类,字段,接口,包括方法的详细信息。反射最主要就是提供动态操作类的机制。
通过反射,程序编译时就不需要确定类,方法,字段等信息,可以在程序运行时动态调用。

通过反射可以绕过访问限制,通过设置AccessibleObject.setAccessible(true)就可以访问私有的字段和方法,带来方便的同时容易引入隐患。

反射API

java的反射API提供了一系列的类和接口来操作Class对象。主要使用包java.lang.reflect。主要使用的类

  • java.lang.Class:表示类的对象,提供方法获取类的信息,包括方法、字段、接口等。

  • java.lang.reflect.Field:表示类的字段(属性),提供方法访问和修改类的字段。

  • java.lang.reflect.Method:表示类的方法,提供方法动态调用类的方法。

  • java.lang.reflect.Constructor:表示类的构造函数,提供方法创建类的对象。

注:
Java 的反射 API 统一使用Object,是为了通用性和兼容性,但你在使用时需要手动进行类型转换

Class类

在java里,Class类是实现反射机制的关键部分,它代表着java中的类与接口。Class类能够让你在运行时获取类的相关信息,像类的属性、方法、构造函数等,还能创建对象、调用方法、访问属性等。
在java中每一个类都有一个对应的class对象,这个对象就是类数据的载体。

类的定义通过class对象引用,通过class对象获取类的基本信息,通过class可以定义

  • 类名

  • 类的构造方法

  • 类的父类和接口

  • 类的字段

每个类(Class)、接口(Interface)、数组(Array)、枚举(Enum)、注解(Annotation)甚至原始类型(如 int、boolean)在运行时都会有一个对应的Class对象。
常见的类型

类型 是否有 Class 对象?
类(如 String) ✅ 有
接口(如 Runnable) ✅ 有
数组(如 int[]) ✅ 有
枚举(如 Thread.State) ✅ 有
注解(如 @Override) ✅ 有
原始类型(如 int) ✅ 有
void ✅ 有(void.class)

定义一个示例类

package com.example; public class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } public void introduce() { System.out.println("Hello, my name is " + name + " and I am " + age + " years old."); } } 

获取Class对象

通过字面量获取对象

Class<?> clazz = Person.class; //获取Person类的Class对象 

类字面量:
类字面量指的是代表类、接口、数组类或者基本数据类型的特殊表达式。类字面量的用途主要是在编译阶段就获得 Class对象。
使用类字面量获取 Class对象时,不会触发类的初始化。只有当真正使用类的静态变量、静态方法或者创建对象时,类才会被初始化。

这里获取到的是Person类的Class对象,类型为class<Person>

通过getclass()方法获取对象

Person micdy = new Person("Micdy", 25); Class<? extends Person> clazz = micdy.getClass(); //获取示例micdy的类的Class对象 

方法需要通过已经实例化过对象的Class获取,其中getClass()Object类中的方法

public class Object { public final native Class<?> getClass(); } // final : 不能被重写 // native : 本地方法由 JVM 用 C/C++ 实现 

每个类在被加载进 JVM 的时候,都会被创建一个唯一的Class对象,JVM 会把这个Class对象放到方法区(Java 8 之前)或元空间(Java 8 之后)中进行缓存。

所以当Person类被加载过后new Person(),JVM 已经加载过Person.class,而每个对象在内存中都会有一个指向自己Class对象的隐藏引用,getClass()就是读取这个引用,返回该对象所属的Class实例,等价于

  • JVM:micdy是个对象,它的类型是Person

  • JVM 给Person创建了Class对象(如果之前没有创建)

  • getClass()从对象内部拿出这个引用并返回它

总结:通过对象的元数据指针访问 JVM 中的唯一 Class 对象(每一个Class对象只会被创建一次),它是由类加载器在类被加载时创建的,并保存在方法区/元空间中。

注:getClass()是运行时行为(必须要创建实例才能反射),依赖对象真实的类型,即使你把Person向上转型为Object,它也能返回正确的运行时类型。

通过Class.forName()方法获取对象

Class.forName("classname") // 静态方法 

这个方法实际调用的是forName0(className, true, ClassLoader.getCallerClassLoader())

private static native Class<?> forName0(String name, boolean initialize, ClassLoader loader) 

native:JVM 实现的本地方法。

  1. 查找类名

  2. 调用ClassLoader.loadClass()加载类

  3. 返回Class对象

示例

Class<?> clazz2 =Class.forName("com.example.Person"); // 完整的类名 

总结

方法 是否需要对象实例 原理
.getClass() ✅ 需要 对象内部指向 Class 元数据
.class ❌ 不需要 编译期已知类型,JVM 自动处理
Class.forName() ❌ 不需要 运行时反射 + 类加载器加载

类加载的几个阶段

阶段
加载(Load) 找到 .class 字节码并读取到内存,生成 Class 对象
验证(Verify) 校验类文件合法性,比如魔数、格式、权限
准备(Prepare) 为静态变量分配内存(默认值)
解析(Resolve) 把符号引用(常量池)替换为真实引用
初始化(Init) 执行静态代码块,赋值静态变量(<clinit>)

注:只有设置了initialize=true(默认)时才执行初始化。

获取构造方法

获取构造方法,就是得到类的Constructor,常用的Constructor方法如下

方法 用途
getName() 获取构造器所属类名
getParameterTypes() 获取参数类型数组
isAccessible()/setAccessible(true) 用于操作私有构造器
newInstance(Object... initargs) 用构造方法创建对象

获取所有的构造方法,包括 private

Constructor<?>[] allConstructors = clazz.getDeclaredConstructors(); 

获取 public 构造方法

获取public构造方法,无法获取private构造方法。

Constructor<?>[] constructors = clazz.getConstructors(); Constructor<?> constructor = clazz.getConstructor(String.class, int.class); //获取指定参数类型的构造方法 

之后可以使用获取的构造器创建实例。

Class<?> clazz = Class.forName("com.example.Person"); Constructor<?> constructor = clazz.getConstructor(String.class, int.class); Object obj = constructor.newInstance("John", 30); Person person = (Person) obj; 

由于这里使用了Class<?>Constructor<?>得到的都是泛型,返回的都是 Object ,不能转换为具体类型,可以手动转换,也可以一开始就不使用泛型,使用Class<Person>Constructor<Person>

Class<Person> clazz = (Class<Person>) Class.forName("com.example.Person"); Constructor<Person> constructor = clazz.getConstructor(String.class, int.class); Person person = constructor.newInstance("John", 30); 

获取 private 构造方法

由于 private 不允许外部 new ,但是可以提供反射调用私有的构造器。要调用私有的构造器只需要

yourconstructor.setAccessible(true); 

即设置构造器可访问,打破反射的访问检查。

在 Java 9+ 中,如果模块未开放,也可能抛出InaccessibleObjectException,可以加 VM 参数来解决:

--add-opens java.base/com.example=ALL-UNNAMED 

即使是单例实例,也可以使用反射机制打破

package com.example; public class Singleton { private static final Singleton instance = new Singleton(); private Singleton() { System.out.println("Singleton constructor called"); } public static Singleton getInstance() { return instance; } } 

可以通过反射获取构造器创建多个实例,打破单例限制

Singleton singleton = Singleton.getInstance(); Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(); constructor.setAccessible(true); Singleton singleton2 = constructor.newInstance(); Singleton singleton3 = constructor.newInstance(); System.out.println(singleton == singleton2); // false System.out.println(singleton2 == singleton3); // false 

在构造器中加入创建的判断就可以防止这种反射攻击

private static boolean instanceCreated = false; private Singleton() { if (instanceCreated) { throw new RuntimeException("别射我!"); } instanceCreated = true; } 

获取字段

获取所有字段

Field[] fields = clazz.getFields(); //不包括private Field[] fields = clazz.getDeclaredFields(); //包括priavte 

获取字段的常用方法

方法 用途
getName() 获取字段名
getType() 获取字段类型
get(Object obj) 获取对象上的字段值
set(Object obj, Object value) 设置字段值
setAccessible(true) 设置可访问(用于访问 private 字段)

和访问私有构造方法相同,访问私有字段也要设置可访问

yourField.setAccessible(true); 

示例

Person person = new Person("Micdy", 20); Field nameField = Person.class.getDeclaredField("name"); //获取name字段 nameField.setAccessible(true); //设置访问 Object nameValue = nameField.get(person); System.out.println("Name: " + nameValue); nameField.set(person, "John Doe"); //修改字段 System.out.println("Updated Name: " + person.getName()); > Name: Micdy Updated Name: John Doe 

修改 final 字段

final字段表示只可复制一次,并且不能修改。
反射是底层机制,可以打破这种限制

示例

Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(nameField, nameField.getModifiers() & ~java.lang.reflect.Modifier.FINAL); 
问题
Java 12+ 开始modifiers字段被移除 上面方法在 Java 12+ 会失败,需要用Unsafe或开 JVM 权限
static final字段更难改 因为可能被 JVM 优化为常量内联,改了没用
修改后访问还是旧值? 可能被缓存或内联,可尝试手动刷新(如反序列化、重新加载类)

获取方法

获取方法的常用方法

方法 用途
getMethods()/getDeclaredMethods() 获取所有方法
getMethod(...)/getDeclaredMethod(...) 获取单个方法
method.invoke(...) 调用方法

设置可以访问私有方法

yoursetAccessible(true); 

示例

Person person = new Person("Micdy", 20); Class<?> clazz = person.getClass(); Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { System.out.println("Method: " + method.getName()); } > Method: getName Method: getAge Method: sayHello Method: introduce Method: greet Method: secretMethod 

调用方法,使用invoke调用方法

Method sayHelloMethod = clazz.getDeclaredMethod("sayHello"); sayHelloMethod.invoke(person); > Hello, my name is Micdy 

获取和调用私有方法,设置访问即可

private void secretMethod() { System.out.println("This is a secret."); } 

示例

Method secretMethod = clazz.getDeclaredMethod("secretMethod"); secretMethod.setAccessible(true); secretMethod.invoke(person); > This is a secret. 

调用静态方法时,要注意参数的传递,第一个参数传入null

public static void greet(String msg) { System.out.println("Static greet: " + msg); } 

示例

Method greetMethod = clazz.getDeclaredMethod("greet", String.class); greetMethod.invoke(null, "Hello from static method!"); //第一个参数传入null 

简单的反射调用工具

public static Object callMethod(Object target, String methodName, Object... args) throws Exception { Class<?> clazz = target.getClass(); Class<?>[] paramTypes = Arrays.stream(args) .map(Object::getClass) .toArray(Class<?>[]::new); Method method = clazz.getDeclaredMethod(methodName, paramTypes); method.setAccessible(true); return method.invoke(target, args); } 

反射执行 exce 语句

Class.forName("java.lang.Runtime").getMethod("exec", String.class).invoke(Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime")),"cmd /c start"); 

4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

打靶日记——NullByte
【THM】offensive-hackpark
检测与应对新型国家级网络持久化攻击技术
高负荷安全运营中心如何有效分级处理威胁情报警报
WordPress User Registration & Membership 权限提升及网站接管利用漏洞详情(CVE-2025-2563)
网络犯罪新宠:SheByte推出q99订阅制钓鱼服务

发布评论