反射机制
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 实现的本地方法。
-
查找类名
-
调用
ClassLoader.loadClass()
加载类 -
返回
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(#换成@)