Java反射

java反射

什么是反射?

简单来说就是再java执行的过程中,能够动态的获取类的信息(类名,方法,属性),并且还能调用方法,修改属性。

获取Class对象

获取Class对象的方法有以下几种:

  1. 根据类名:类名.class
  2. 根据对象:对象.getclass()
  3. 根据全限定类名:Class.forName(全路径类名)
  4. 通过类加载器获取class对象:ClassLoader.getSystemClassLoader().loadClass(“com.example.xxx”);

下面举一个例子:

首先创建User.java:

1
2
3
4
5
6
7
8
9
10
11
12
package org.example.entity;

public class User{
public String name = "fsrm";

public String getName() {
return name;
}
public void setName(String testStr) {
this.name = name;
}
}

再创建一个Getclass.java,分别对应着获取class对象的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.example;
import org.example.entity.User;


public class GetClass {
public static void main(String[] args) throws ClassNotFoundException {
//1.通过类名获取类
Class c1 = User.class;
System.out.println(c1);
User user = new User();
//2.通过对象获取类
Class c2 = user.getClass();
System.out.println(c2);
//3.通过全限定类名
Class c3 = Class.forName("org.example.entity.User");
System.out.println(c3);
//通过类加载器获取类名
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class c4 = classLoader.loadClass("org.example.entity.User");
System.out.println(c4);
}
}

image-20251024111639491

比较常用的是第三种方法,通过Class.forNmae(“xxx”)获取类名。

Java反射API

java.lang.Class

用来描述类的内部信息,Class的实例可以获取类的包、注解、修饰符、名称、超类、接口等。

https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html

https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/package-summary.html

方法名 释义
getPackage() 获取该类的包
getDeclaredAnnotations() 获取该类上所有注解
getModifiers() 获取该类上的修饰符
getName() 获取类名称
getSimpleName() 获取简单类名称
getGenericSuperclass() 获取直属超类
getGenericInterfaces() 获取直属实现的接口
newInstance() 根据构造函数创建一个实例
更多方法可查看官方文档……
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package org.example.reflectdemo;

import java.lang.reflect.Modifier;

public class ClassDemo {
public static void main(String[] args) throws ClassNotFoundException {
Class clazz = Class.forName("org.example.reflectdemo.UserInfo");
//获取当前类所在的包路径
Package aPackage = clazz.getPackage();
System.out.println("当前类的包名是:"+aPackage);

//获取类上的修饰符
int modifiers = clazz.getModifiers();
String modifierStr = Modifier.toString(modifiers);
System.out.println("当前类的修饰符是:"+modifierStr);

//获取类名称
String className = clazz.getName();
System.out.println("当前类的名称是:"+className);

//获取简单类名称
String simpleName = clazz.getSimpleName();
System.out.println("当前类的简单名称是:"+simpleName);

}
}

image-20251031202050082

java.lang.reflect.Field

提供了类的属性信息。可以获取属性上的注解、修饰符、属性类型、属性名等。

https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Field.html

方法名 释义
getField(“xxx”) 获取目标类中声明为 public 的属性
getFields() 获取目标类中所有声明为 public 的属性
getDeclaredField(“xxx”) 获取目标类中声明的属性
getDeclaredFields() 获取目标类中所有声明的属性
更多方法可查看官方文档……
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package org.example.reflectdemo;

import java.lang.reflect.Field;

public class FieldDemo {
public static void main(String[] args) throws Exception{
//获取一个该类或父类中声明为 public 的属性
Class <?> clazz = Class.forName("org.example.reflectdemo.UserInfo");
Field field1 = clazz.getField("age");
System.out.println("get field结果"+ field1);
// 获取该类及父类中所有声明为 public 的属性
Field[] filedArray = clazz.getFields();
for(Field field : filedArray) {
System.out.println("get fields结果" + field);
}
//获取一个类中声明的属性
Field field2 = clazz.getDeclaredField("age");
System.out.println("getDeclaredField结果"+ field2);

//获取一个类中声明的所有属性
Field[] declareAaary = clazz.getDeclaredFields();
for(Field declareField : declareAaary){
System.out.println("getDeclaredFields结果"+ declareField);
}
}
}

image-20251031205337523

修改字段值

1.修改public属性的值。

image-20251111092021518

这个定义为20,利用反射修改它的值,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
 Field field2 = clazz.getDeclaredField("age");
UserInfo user = new UserInfo();
field2.set(user, 30);
Object name = field2.get(user);
System.out.println("getDeclaredField结果"+ name);

或者是
Method method = clazz.getMethod("getAge");
Object obj = method.invoke(null);
Field field2 = clazz.getField("age");
field2.set(obj, 30);
Object obj1 = field2.get(obj);
System.out.println(obj1); 前提是getAge方法是静态的 并且它是返回实例化之后的对象的

image-20251111092957867

如果是修改私有属性的值,代码如下:

1
2
3
4
5
6
Field field2 = clazz.getDeclaredField("name");
field2.setAccessible(true);
UserInfo user = new UserInfo();
field2.set(user, "李四");
Object name = field2.get(user);
System.out.println("getDeclaredField结果"+ name);

修改final定义的值。

java.lang.reflect.Method

提供了类的方法信息。可以获取方法上的注解、修饰符、返回值类型、方法名称、所有参数。

官方文档:https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Method.html

方法名 释义
getMethod(“setAge”, String.class) 获取目标类及父类中声明为 public 的方法,需要指定方法的入参类型
getMethods() 获取该类及父类中所有声明为 public 的方法
getDeclaredMethod() 获取一个在该类中声明的方法
getDeclaredMethods() 获取所有在该类中声明的方法
getParameters() 获取所有传参
更多方法可查看官方文档……
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package org.example.reflectdemo;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class MethodDemo {
public static void main(String[] args) throws Exception {
Class <?> clazz = Class.forName("org.example.reflectdemo.UserInfo");
//1 获取一个该类及父类中声明为 public 的方法,需要指定方法的入参类型
Method method = clazz.getMethod("setName", String.class);
System.out.println("01-getMethod运行结果:" + method);

//2获取一个该类及父类中所有声明为public的方法
Method[] methods = clazz.getMethods();
for (Method m : methods){
System.out.println("02-getMethods运行结果:" + m);
}

// 3获取所有入参
Parameter[] parameters = method.getParameters();
for(Parameter p : parameters){
System.out.println("03-getParameters运行结果:" + p);
}

//4获取一个在该类中声明的方法
Method DeclareMethod = clazz.getDeclaredMethod("getName");
System.out.println("04-getDeclaredMethod运行结果:" + DeclareMethod);

//5获取所有在该类中声明的方法
Method[] DeclareMethods = clazz.getDeclaredMethods();
for(Method declareMethod : DeclareMethods){
System.out.println("05-getDeclaredMethods运行结果:" + declareMethod);
}

}
}

image-20251031222547354

java.lang.reflect.Modifier

提供了访问修饰符信息。通过ClassFieldMethodConstructor等对象都可以获取修饰符,这个访问修饰符是一个整数,可以通过Modifier.toString方法来查看修饰符描述。并且该类提供了一些静态方法和常量来解码访问修饰符。

官方文档:https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Modifier.html

方法名 释义
getModifiers() 获取类的修饰符值
getDeclaredField(“username”).getModifiers() 获取属性的修饰符值
更多方法可查看官方文档……
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.example.reflectdemo;

import java.lang.reflect.Modifier;

public class ModifierDemo {
public static void main(String[] args) throws Exception {
Class <?> clazz = Class.forName("org.example.reflectdemo.UserInfo");
//1. 获取类的修饰符值
int modifiers = clazz.getModifiers();
System.out.println("获取类的修饰符值getModifiers运行结果:" + modifiers);
//2. 获取属性的修饰符值
int modifiers1 = clazz.getDeclaredField("name").getModifiers();
System.out.println("获取属性的修饰符号getDeclaredField运行结果:" + modifiers1);
//3. 获取方法的修饰符值
int modifers2 = clazz.getDeclaredMethod("introduce").getModifiers();
System.out.println("获取方法的修饰符号getMethod运行结果:" + modifers2);
//4 根据修饰符值获取修饰符标志的字符串
String modifersStr = Modifier.toString(modifers2);
System.out.println("根据修饰符值获取修饰符标志的字符串:" + modifersStr);

}
}

image-20251101111012205

java.lang.reflect.Constructor

提供了类的构造函数信息。可以获取构造函数上的注解信息、参数类型等。

官方文档:https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Constructor.html

方法名 释义
getConstructor() 获取一个声明为 public 构造函数实例
getConstructors() 获取所有声明为 public 构造函数实例
getDeclaredConstructor() 获取一个声明的构造函数实例
getDeclaredConstructors() 获取所有声明的构造函数实例
更多方法可查看官方文档……
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package org.example.reflectdemo;

import java.lang.reflect.Constructor;

public class ConstructorDemo {
public static void main(String[] args) throws Exception {
Class <?> clazz = Class.forName("org.example.reflectdemo.UserInfo");
//获取一个声明为 public 构造函数实例
Constructor<?> constructor1 = clazz.getConstructor(String.class, int.class);
System.out.println("01-constructor1: "+constructor1);

// 根据构造函数创建一个实例
Object obj1 = constructor1.newInstance("张三", 20);
System.out.println("02-obj1: "+obj1);

//获取所有声明为public构造函数实例
Constructor<?> constructorArray1[] = clazz.getConstructors();
for(Constructor<?> constructor : constructorArray1){
System.out.println("03-constructor: "+constructor);
}

//获取一个构造函数实例
Constructor<?> constructor2 = clazz.getDeclaredConstructor(int.class);
System.out.println("04-constructor2: "+constructor2);
constructor2.setAccessible(true);
Object obj2 = constructor2.newInstance(66);
System.out.println("05-obj2: "+obj2);

//获取所有构造函数实例
Constructor<?> constructorArray2[] = clazz.getDeclaredConstructors();
for(Constructor<?> constructor : constructorArray2){
System.out.println("06-constructor: "+constructor);
}
}
}

image-20251105102503804

java.lang.reflect.AccessibleObject

FieldMethodConstructor类的超类。该类提供了对类、方法、构造函数的访问控制检查的能力(如:私有方法只允许当前类访问)。

访问检查在设置/获取属性、调用方法、创建/初始化类的实例时执行。

方法名 释义
setAccessible() 将可访问标志设为true(默认为false),会关闭访问检查。这样即使是私有的属性、方法或构造函数,也可以访问。

当访问某个私有属性的值,或者需要修改某个私有属性值的时候,就受到访问限制的约束,这个时候就要利用setAccessible 使私有属性内容可修改或可访问。

image-20251105111252842

image-20251105111307307

image-20251105111317383

newInstance()

将获取到的对象实例化。调用的是这个类的无参构造函数。

使用 newInstance 不成功的话可能是因为:①、你使用的类没有无参构造函数,②、你使用的类构造函数是私有的。

参考文章:https://mp.weixin.qq.com/s/c7myV7OurX26hx10fqeLdA?scene=1

Java反射到命令执行(java.lag.Runtime)

通过getMethod

先说明一下有关invoke的理解

invoke中第一个参数需要是一个实例化,或者是静态方法的调用。

先说一下静态方法的调用:

1
2
3
4
5
6
7
8
9
10
11
12
package org.example.entity;


import java.lang.reflect.Method;

public class test {
public static void main(String[] args) throws Exception {
Class <?> clazz = Class.forName("java.lang.Runtime");
Method method1 = clazz.getMethod("getRuntime");
Object obj = method1.invoke(clazz); // 相当于调用了java.lang.Runtime下的getRuntime()方法 也就是Runtime.getRuntime()
}
}

java.lang.Runtime中的getRuntime方法是静态方法。

image-20251110210613734

image-20251110210738635

所以再进行调用invoke的时候,第一个参数可以是null,也可以是clazz.

那么另外一种情况,第一个参数是实例化的时候。

1
2
3
Class <?> clazz = Class.forName("java.lang.Runtime");
Method method = clazz.getMethod("exec",String.class);
System.out.println(method);

image-20251110211041574

image-20251110210958048

这个方法就不是静态方法了,那么第一个参数就需要是经过实例化的。

1
2
3
Class <?> clazz = Class.forName("java.lang.Runtime");
Method method = clazz.getMethod("exec",String.class);
Object obj = method.invoke(Runtime.getRuntime(),"calc"); //调用Runtime下的exec方法,并将calc传入参数中

image-20251110211239575

下面是一些私有类的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package org.example.reflectdemo;
import org.example.reflectdemo.UserInfo;

import java.lang.reflect.Method;

public class invokeDemo {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("org.example.reflectdemo.UserInfo");
Method method = clazz.getDeclaredMethod("introduce");
method.setAccessible(true);
Object obj = method.invoke(new UserInfo("张三",25));
System.out.println(obj);
}
}

image-20251110212624804

下面则是利用getMethod反射调用exec执行命令的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.example.codeexec;

import java.lang.reflect.Method;

public class RuntimegetMethod {
public static void main(String[] args) throws Exception {
Class <?> clazz = Class.forName("java.lang.Runtime");
Method method1 = clazz.getMethod("getRuntime");
Method method2 = clazz.getMethod("exec", String.class);
Object obj = method1.invoke(null);
method2.invoke(obj, "calc");
}
}

image-20251110212950411

合在一起的:

1
2
3
4
 public static void main(String[] args) throws Exception {
Class <?> clazz = Class.forName("java.lang.Runtime");
clazz.getMethod("exec", String.class).invoke(clazz.getMethod("getRuntime").invoke(null),"calc");
}

通过getDeclaredConstructor(高版本jdk绕过反射限制)

这个就设计到高版本jdk绕过反射的限制,也就是再调用一些私有类的时候,绕过这个限制。

JDK 9 引入了模块系统,其中包括java.lang, java.io, java.util 等包。

java.lang.*(包括 Runtime、ProcessImpl、System 等)属于模块 java.base

java.base 明确地「封装」了这些包,不对外开放反射访问

image-20251111210920540

是这个setAccessible的问题,跟进去看下。

image-20251113174836509

这里的意思是返回调用setAccessible方法的类,比如说是RuntimeDeclaredConstructor类调用的这个方法,那么返回的就是RuntimeDeclaredConstructor

image-20251113193210561

跟进checkCanSetAccessible。

到最后跟进的的方法如下:

image-20251113193322790

image-20251113193439957

要想返回true的话,一共有6个if条件。

先说前三个:

1
2
3
if (callerModule == declaringModule) return true;  //如果调用者的模块和声明类的模块一致,则返回true
if (callerModule == Object.class.getModule()) return true; //如果调用者的模块是java.base的话,则返回true
if (!declaringModule.isNamed()) return true; //如果声明类的模块是未命名的,则返回true

image-20251113195211035

后面三个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
boolean isClassPublic = Modifier.isPublic(declaringClass.getModifiers());
if (isClassPublic && declaringModule.isExported(pn, callerModule)) {
// member is public
if (Modifier.isPublic(modifiers)) {
return true;
} //声明类是public,并且声明类所在的包pn已通过module-info.java中的exports对调用方模块开放。

// member is protected-static
if (Modifier.isProtected(modifiers)
&& Modifier.isStatic(modifiers)
&& isSubclassOf(caller, declaringClass)) {
return true; //如果声明类所调用的是保护属性,并且是静态方法,调用者是声明类的子类的话,就返回true.
}
}

// package is open to caller
if (declaringModule.isOpen(pn, callerModule)) {
return true; // 如果声明包通过 opens 对调用方模块开放,则调用方可以通过反射访问包括私有成员在内的所有内容。
}

所以要想绕过jdk下反射的限制,就要满足上面任意一个if条件即可。

在 jdk 9 后的模块化机制提到

1
Note that the sun.misc and sun.reflect packages are available for reflection by tools and libraries in all JDK releases, including JDK 17.

sun.misc和sun.reflect包下的我们是可以正常反射的,关键利用点就在这里。

这里利用的是sun.misc.Unsafe,将调用类中的模块改为java.base

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import com.sun.tools.javac.Main;
import sun.misc.Unsafe;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;


public class test {
public static void main(String[] args) throws Exception {
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null); //实例化了Unsafe这个类。
System.out.println(unsafe);
Module baseModule = Object.class.getModule(); //则是获取java,base模块,参考上面的if判断。
System.out.println(baseModule);
Class<?> currentClass = test.class; //这个是获取当前调用者的类。
System.out.println(currentClass);
long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));//获取 Class 对象中 "module" 字段的内存偏移量。
System.out.println(addr);
System.out.println(unsafe.getAndSetObject(currentClass, addr, baseModule));//直接将当前类的 module 字段改成 java.base 模块,从而绕过模块访问限制。
}
}

image-20251113202118413

image-20251113201730021

唯一要变的是 Class<?> currentClass = test.class; 根据当前的类名不同,进行修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package org.example.codeexec;

import sun.misc.Unsafe;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class RuntimeDeclaredConstructor {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("java.lang.Runtime");
Constructor<?> m = clazz.getDeclaredConstructor();
Class<?> clazz1 = Class.forName("sun.misc.Unsafe");
Field field = clazz1.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
Module module = Object.class.getModule();
Class<?> Run = RuntimeDeclaredConstructor.class;
long addr= unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(Run, addr, module);
m.setAccessible(true);
Method c1 = clazz.getMethod("exec", String.class);
c1.invoke(m.newInstance(), "calc.exe");
}
}

image-20251113171527997

反射之java.lang.ProcessBuilder

ProcessBuilder进行命令执行的大概思路如下:

首先通过command设置命令执行的参数,如calc等,最后start调用this.command中的参数信息,从而进行命令执行。

ProcessBuilder中传递 this.command 大概有4个 其中两个构造函数,两个普通函数。

ProcessBuilder有两个构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
public ProcessBuilder(List<String> command) {
if (command == null)
throw new NullPointerException();
this.command = command;
}

public ProcessBuilder(String... command) {
this.command = new ArrayList<>(command.length);
for (String arg : command)
this.command.add(arg);
}

利用构造函数进行命令执行如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
Class<?> clazz = Class.forName("java.lang.ProcessBuilder");
Constructor<?> constructor = clazz.getDeclaredConstructor(String[].class);
Object obj = constructor.newInstance((Object) new String[] {"calc"});
Method method = clazz.getMethod("start");
method.invoke(obj);

Class<?> clazz = Class.forName("java.lang.ProcessBuilder");
Constructor<?> constructor = clazz.getDeclaredConstructor(List.class);
List<String> cmdlist = new ArrayList<>();
cmdlist.add("calc");
Object obj = constructor.newInstance((cmdlist));
Method method = clazz.getMethod("start");
method.invoke(obj);

两个普通函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
public ProcessBuilder command(List<String> command) {
if (command == null)
throw new NullPointerException();
this.command = command;
return this;
}

public ProcessBuilder command(String... command) {
this.command = new ArrayList<>(command.length);
for (String arg : command)
this.command.add(arg);
return this;
}

利用普通函数进行命令执行代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Class<?> clazz = Class.forName("java.lang.ProcessBuilder");

Method method1 = clazz.getDeclaredMethod("command", String[].class);
Method method2 = clazz.getDeclaredMethod("start");
ProcessBuilder processBuilder = new ProcessBuilder();
Object obj =method1.invoke(processBuilder,(Object) new String[] {"calc"});
method2.invoke(processBuilder);


Class<?> clazz = Class.forName("java.lang.ProcessBuilder");

Method method1 = clazz.getDeclaredMethod("command", List.class);
Method method2 = clazz.getDeclaredMethod("start");
ProcessBuilder processBuilder = new ProcessBuilder();
List<String> cmdlist = new ArrayList<>();
cmdlist.add("calc");
Object obj =method1.invoke(processBuilder, cmdlist);
method2.invoke(processBuilder);

Java反射
http://example.com/2025/11/13/java反射/
作者
FSRM
发布于
2025年11月13日
更新于
2025年11月13日
许可协议