需求引出反射 为什么会有反射这个机制,来看看下面的例子。
根据配置文件re.properties指定信息,创建Dog对象并调用方法hi
这样的需求在学习框架时特别多,即通过外部文件配置,在不修改源码情况下,来控制程序,符合设计模式的ocp原则(开闭原则:不修改源码,扩容功能) 配置文件内容:
1 2 classfullpath =com.test.Dog method =hi
Dog类
1 2 3 4 5 6 7 8 9 package com.test; public class Dog { public String name = "旺财" ; public void hi () { System.out.println("hi" + name); } public void say () { System.out.println("say hello" ); }
测试代码
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 package com.ref; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Properties; @SuppressWarnings({"all"}) public class ReflectionQuestion { public static void main (String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { properties.load(new FileInputStream ("src\\re.properties" )); String classfullpath = properties.get("classfullpath" ).toString(); String methodName = properties.get("method" ).toString(); System.out.println("classfullpath=" + classfullpath); System.out.println("method=" + methodName); Class cls = Class.forName(classfullpath); Object o = cls.newInstance(); System.out.println("o 的运行类型=" + o.getClass()); Method method1 = cls.getMethod(methodName); System.out.println("=============================" ); method1.invoke(o); } }
运行结果 现在我们可以不修改源码,来调用其他的方法,修改配置文件 将
1 2 classfullpath =com.test.Dog method =say
在传统方法下,我们必须修改源码才能实现,有了反射机制,我们修改配置文件即可实现。
Java反射机制 Java Reflection
反射机制允许程序在执行期借助于ReflectionAPI取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到。
加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息。通过这个对象得到类的结构。这个Class对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为:反射。
1 2 Person对象 p --> 类型Person类 Class对象 cls ---> 类型Class类
Java 反射机制原理示意图
Java 反射机制作用
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时得到任意一个类所具有的成员变量和方法
在运行时调用任意一个对象的成员变量和方法
生成动态代理
反射相关的主要类
java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
java.lang.reflect.Method:代表类的方法,Method对象表示一个类的一个方法
java.lang.reflect.Field:代表类的成员变量,Field对象表示一个类的一个成员变量
java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示一个类的一个构造器 除了 Class 在 java.lang 包中,其余反射相关类(如 Method、Field、Constructor)都在 java.lang.reflect 包中
反射优点和缺点 优点:可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑 缺点:使用反射基本是解释执行,执行速度有影响
反射调用优化 - 关闭访问检查
Method和Field、Constructor对象都有setAccessible()方法
setAccessible作用是启动和禁用访问安全检查的开关
参数值为true 表示反射对象在使用时取消访问检查 ,提高反射的效率。参数值为false 则表示反射的对象执行访问检查 测试代码
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 37 38 39 40 41 42 43 44 45 46 47 import com.test.Dog; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class Reflection02 { public static void m1 () { Dog dog = new Dog (); long start = System.currentTimeMillis(); for (int i = 0 ; i < 90 ; i++) { dog.hi(); } long end = System.currentTimeMillis(); System.out.println("m1() 耗时=" + (end - start) + " 传统" ); } public static void m2 () throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { Class cls = Class.forName("com.test.Dog" ); Object o = cls.newInstance(); Method hi = cls.getMethod("hi" ); long start = System.currentTimeMillis(); for (int i = 0 ; i < 900000000 ; i++) { hi.invoke(o); } long end = System.currentTimeMillis(); System.out.println("m2() 耗时=" + (end - start) + " 反射" ); } public static void m3 () throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { Class cls = Class.forName("com.test.Dog" ); Object o = cls.newInstance(); Method hi = cls.getMethod("hi" ); hi.setAccessible(true ); long start = System.currentTimeMillis(); for (int i = 0 ; i < 900000000 ; i++) { hi.invoke(o); } long end = System.currentTimeMillis(); System.out.println("m3() 耗时=" + (end - start) + " 反射优化" ); } public static void main (String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { m1(); m2(); m3(); } }
测试结果,优化后对效率的提升其实并不是很大
Class类 基本介绍
Class也是类,因此也继承Object类
Class类对象不是new出来的,而是系统创建的 new对象调试过程如下 通过反射去得到对象,调试过程如下
对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
每个类的实例都会记得自己是由哪个Class实例所生成
通过Class对象可以完整地得到一个类的完整结构,通过一系列API
Class对象是存放在堆的
类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括方法代码,变量名,方法名,访问权限等等) 测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.test; import java.util.ArrayList; public class Class01 { public static void main (String[] args) throws ClassNotFoundException { Class cls1 = Class.forName("com.test.Dog" ); Class cls2 = Class.forName("com.test.Dog" ); System.out.println(cls1.hashCode()); System.out.println(cls2.hashCode()); Class cls3 = Class.forName("com.test.Dog" ); System.out.println(cls3.hashCode()); } }
Class类常用方法
方法名
功能说明
static Class forName(String name)
返回指定类名 name 的 Class 对象
Object newInstance()
调用缺省构造函数,返回该 Class 对象的一个实例
getName()
返回此 Class 对象所表示的实体(类、接口、数组类、基本类型等)名称
Class[] getInterfaces()
获取当前 Class 对象的接口
ClassLoader getClassLoader()
返回该类的类加载器
Class getSuperclass()
返回表示此 Class 所表示的实体的超类的 Class
Constructor[] getConstructors()
返回一个包含某些 Constructor 对象的数组
Field[] getDeclaredFields()
返回 Field 对象的一个数组
Method getMethod(String name, Class... paramTypes)
返回一个 Method 对象,此对象的形参类型为 paramType
还有一些常用方法的演示 此时目录结构如下: 测试代码
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 package com.test; import com.entity.Car; import java.lang.reflect.Field; public class Class02 { public static void main (String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException { String classAllPath = "com.entity.Car" ; System.out.println(cls.getClass()); System.out.println(cls.getPackage()); System.out.println(cls.getName()); Car car = (Car)cls.newInstance(); System.out.println(car); Field field = cls.getField("brand" ); System.out.println(field.get(car)); System.out.println(field.get(car)); Field[] fields = cls.getFields(); for (Field f : fields) { System.out.print(f.get(car) + "\t" ); } }}
获取Class类对象
已知全类名且在类路径下:Class cls1 = Class.forName("java.lang.Cat"); 用途:配置文件读取全路径加载类,可能抛 ClassNotFoundException。
已知具体类:Class cls2 = Cat.class; 用途:参数传递,安全可靠,性能最高。
基本数据类型:Class cls = int.class; // 同理支持 char、boolean、float、double、byte、long、short
包装类:Class cls = Integer.TYPE; // 返回对应基本类型的 Class
已有对象实例:Class cls3 = 对象.getClass(); // 运行类型 用途:通过已有对象获取 Class。
用类加载器:ClassLoader cl = 对象.getClass().getClassLoader();Class cls4 = cl.loadClass("类的全类名"); 测试代码
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 37 38 package com.test; import com.entity.Car; public class GetClass { public static void main (String[] args) throws ClassNotFoundException { String classAllPath = "com.entity.Car" ; Class<?> cls1 = Class.forName(classAllPath); System.out.println(cls1); Class<?> cls2 = Car.class; System.out.println(cls2); Car car = new Car (); Class<?> cls3 = car.getClass(); System.out.println(cls3); Class<?> cls4 = classLoader.loadClass(classAllPath); System.out.println(cls4); System.out.println(cls1.hashCode()); System.out.println(cls2.hashCode()); System.out.println(cls3.hashCode()); System.out.println(cls4.hashCode()); Class<Integer> integerClass = int .class; Class<Character> characterClass = char .class; Class<Boolean> booleanClass = boolean .class; System.out.println(integerClass); Class<Character> type2 = Character.TYPE; System.out.println(type1); System.out.println(integerClass.hashCode()); System.out.println(type1.hashCode()); } }
哪些类型有Class对象
外部类(顶层普通类)
成员内部类(非 static,属于对象)
静态内部类(static,属于类)
局部内部类(方法/代码块内定义)
匿名内部类(无名字,一次性实现)
接口(interface)
数组(任何维度,如 int[]、String[][])
枚举(enum)
注解(@interface)
基本数据类型(int、char、boolean…)
void(作为“返回类型”的 Class,单例 Void.TYPE) 测试代码
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package com.test; public class Class03 { public static class Outer { } public class MemberInner { } public static class StaticInner { } public void local () { class LocalInner { } print("局部内部类" , LocalInner.class); Runnable anon = new Runnable () { @Override public void run () { } }; print("匿名内部类" , anon.getClass()); } interface MyInterface { } enum MyEnum {A, B} @interface MyAnno { } public static void main (String[] args) throws NoSuchFieldException { print("外部类" , Outer.class); print("成员内部类" , MemberInner.class); print("静态内部类" , StaticInner.class); new Class03 ().local(); print("接口" , MyInterface.class); print("枚举" , MyEnum.class); print("注解" , MyAnno.class); int [] arr = new int [0 ]; print("数组" , arr.getClass()); print("基本数据类型(int)" , int .class); print("void" , void .class); } private static void print (String label, Class<?> cls) { System.out.printf("%-12s -> %s%n" , label, cls); } }
类加载 反射机制是 Java 实现“动态语言”特性的关键,它允许运行时动态加载类 。
静态加载 编译阶段就把相关类加载进来;如果类不存在,编译直接报错 ,依赖性强。
动态加载 运行阶段真正用到时才加载 ;即使类文件缺失,只要执行路径没走到,就不会报错 ,依赖性降低。
1 2 3 4 5 6 7 8 9 10 Java 类加载时机对比: 1. 创建对象时(new ) → 静态加载 2. 子类被加载时,其父类也会立即被加载 → 静态加载 3. 调用类中的静态成员(静态变量/静态方法) → 静态加载 4. 通过反射: `Class.forName("com.entity.Dog" )` → 动态加载
通过反射获取类的结构信息 java.lang.Class类
getName: 获取全类名
getSimpleName: 获取简单类名
getFields: 获取所有public修饰的属性,包含本类以及父类的
getDeclaredFields: 获取本类中所有属性
getMethods: 获取所有public修饰的方法,包含本类以及父类的
getDeclaredMethods: 获取本类中所有方法
getConstructors: 获取本类所有public修饰的构造器
getDeclaredConstructors: 获取本类中所有构造器
getPackage: 以Package形式返回包信息
getSuperClass: 以Class形式返回父类信息
getInterfaces: 以Class[]形式返回接口信息
getAnnotations: 以Annotation[]形式返回注解信息
java.lang.reflect.Field类
getModifiers: 以int形式返回修饰符
说明 : 默认修饰符是0,public是1,private是2,protected是4,static是8,final是16,public(1) + static(8) = 9
getType: 以Class形式返回类型
getName: 返回属性名
java.lang.reflect.Method类
getModifiers: 以int形式返回修饰符
说明 : 默认修饰符是0,public是1,private是2,protected是4,static是8,final是16
getReturnType: 以Class形式获取返回类型
getName: 返回方法名
getParameterTypes: 以Class[]返回参数类型数组
java.lang.reflect.Constructor类
getModifiers: 以int形式返回修饰符
getName: 返回构造器名(全类名)
getParameterTypes: 以Class[]返回参数类型数组
通过反射创建对象 创建对象的两种方式
方式一 :调用类中的public修饰的无参构造器
方式二 :调用类中的指定构造器
Class类相关方法
newInstance:调用类中的无参构造器,获取对应类的对象
getConstructor(Class... cls):根据参数列表,获取对应的public构造器对象
getDeclaredConstructor(Class... cls):根据参数列表,获取对应的所有构造器对象
Constructor类相关方法
setAccessible:暴破 (感觉翻译的好奇怪)
newInstance(Object... obj):调用构造器
通过反射访问类中的成员 访问属性
根据属性名获取 Field 对象
1 Field f = cls.getDeclaredField(属性名);
爆破:设置 Field 为可访问
访问
1 2 f.set(o, 值); System.out.println(f.get(o));
注意:如果是静态属性,则 set 和 get 中的参数 o,可以写成 null
访问方法
根据方法名和参数列表获取 Method 方法对象
1 2 Method m = cls.getDeclaredMethod(方法名, 参数类型.class);
获取对象:创建类的实例
1 Object o = cls.newInstance();
爆破:设置 Method 为可访问
访问:调用方法
1 Object returnValue = m.invoke(o, 实参列表);
注意:如果是静态方法,则 invoke 的参数 o,可以写成 null 补充:
cls.getDeclaredField(属性名) 和 cls.getDeclaredMethod(方法名, 参数类型.class) 可能抛出 NoSuchFieldException 和 NoSuchMethodException 异常,因此在实际使用中需要进行异常处理。
cls.newInstance() 方法在Java 9之后已被弃用,推荐使用 cls.getDeclaredConstructor().newInstance() 来创建实例。
System.out.println(f.get(o)); 用于打印获取到的属性值。
反射与安全 攻击面与利用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class SecretClass { private String secret = "机密信息" ; private void secretMethod () { System.out.println("这是私有方法" ); } } Class<?> cls = SecretClass.class; SecretClass obj = new SecretClass ();Field f = cls.getDeclaredField("secret" );f.setAccessible(true ); String secret = (String) f.get(obj);Method m = cls.getDeclaredMethod("secretMethod" );m.setAccessible(true ); m.invoke(obj);
1 2 3 Class<?> c = Class.forName("com.example.Plugin" ); Object o = c.getDeclaredConstructor().newInstance();if (o instanceof Runnable) ((Runnable) o).run();
1 2 3 Method getRt = Class.forName("java.lang.Runtime" ).getMethod("getRuntime" );Object rt = getRt.invoke(null );rt.getClass().getMethod("exec" , String.class).invoke(rt, "calc.exe" );
风险与影响
访问控制绕过:破坏封装,读写 private 字段、调用私有方法
类型安全绕过:运行期结构/类型变更导致 ClassCastException 等
性能开销:反射比直接调用慢,频繁使用影响性能
策略绕过:在弱隔离/沙箱中绕过限制
常见攻击链
Commons Collections:利用 InvokerTransformer/ChainedTransformer 触发链式反射到 Runtime.exec
Ysoserial - 最权威的 Java 反序列化利用工具https://github.com/frohoff/ysoserial 包含 CommonsCollections1-7 等多条利用链的完整实现 查看 src/main/java/ysoserial/payloads/CommonsCollections*.java Commons Collections 官方仓库
https://github.com/apache/commons-collections 可以查看源码和历史版本 Java 反序列化漏洞学习资源
https://github.com/GrrrDog/Java-Deserialization-Cheat-Sheet 包含 Commons Collections 利用链的详细说明 CommonsCollections 利用链分析和 PoC
https://github.com/Maskhe/javasec 中文 Java 安全学习资源,包含详细的 CC 链分析 Java 反序列化漏洞实验环境
Fastjson:反射调用 setter/构造器,结合自动类型与 gadget 链造成任意代码执行
Fastjson 官方仓库https://github.com/alibaba/fastjson 官方仓库,可以查看历史漏洞修复记录 Fastjson 漏洞利用和 Gadget 链研究
https://github.com/LeadroyaL/fastjson-blacklist 收集了 Fastjson 的黑名单绕过技巧和 gadget 链 Fastjson 漏洞复现环境
https://github.com/vulhub/vulhub/tree/master/fastjson Vulhub 提供的 Fastjson 漏洞复现环境 Fastjson 反序列化漏洞利用工具
https://github.com/c0ny1/FastjsonExploit Fastjson 反序列化漏洞利用工具 Fastjson 漏洞分析和 PoC 集合
https://github.com/safe6Sec/Fastjson 包含多个版本的漏洞分析和 PoC Ysoserial - Java 反序列化 Gadget 链工具
反射的应用场景 1. 插件系统 许多产品需要支持用户通过插件扩展功能,插件的具体实现在运行时由用户提供。程序在设计时并不确定会加载哪些插件类或调用哪些方法,而是在运行时动态加载插件类,并通过反射调用相关方法。以IDEA插件为例:
插件以JAR包形式存在,包含具体实现类和plugin.xml配置文件
IDEA使用反射配合类加载器动态加载插件入口类
实例化插件对象并调用其方法
2. 框架开发 Spring、Hibernate等框架大量使用反射:
依赖注入(DI):通过反射创建对象并注入依赖
AOP(面向切面编程):动态代理需要反射支持
ORM映射:通过反射将数据库记录映射为Java对象
3. 序列化与反序列化 JSON、XML等数据格式的序列化框架(如Jackson、Gson)使用反射:
4. 单元测试 测试框架(如JUnit)使用反射:
反射实战示例 示例1:基本概念演示 反射允许程序在运行时动态地访问类的结构和行为,包括类、方法、字段、构造函数等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Person { private String name; private int age; public Person (String name, int age) { this .name = name; this .age = age; } public void introduce () { System.out.println("My name is " + name + " and I am " + age + " years old." ); } public int getAge () { return age; } }
示例2:获取Class对象的三种方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public static void main (String[] args) throws Exception { Class<?> clazz1 = Person.class; Class<?> clazz2 = Class.forName("com.test.Person" ); Person person = new Person ("John" , 30 ); Class<?> clazz3 = person.getClass(); System.out.println(clazz1 == clazz2); System.out.println(clazz2 == clazz3); }
示例3:获取构造方法并创建对象 1 2 3 4 5 6 7 8 9 10 11 12 13 public static void main (String[] args) throws Exception { Class<?> clazz = Person.class; Constructor<?>[] constructors = clazz.getConstructors(); Constructor<?> constructor = clazz.getConstructor(String.class, int .class); Object personInstance = constructor.newInstance("John" , 30 ); System.out.println(personInstance); }
示例4:获取和操作字段 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public static void main (String[] args) throws Exception { Person person = new Person ("erosion2020" , 14 ); Class<?> clazz = Person.class; Field[] pubFields = clazz.getFields(); Field[] fields = clazz.getDeclaredFields(); Field name = clazz.getDeclaredField("name" ); name.setAccessible(true ); name.set(person, "AcidEtch" ); System.out.println(name.get(person)); }
示例5:获取和调用方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public static void main (String[] args) throws Exception { Class<?> clazz = Person.class; Method[] pubMethods = clazz.getMethods(); Method[] methods = clazz.getDeclaredMethods(); String name = "AcidEtch" ; Method substring = String.class.getMethod("substring" , int .class); System.out.println(substring.invoke(name, 3 )); Method parseInt = Integer.class.getMethod("parseInt" , String.class); Integer n = (Integer) parseInt.invoke(null , "114514" ); System.out.println(n); }
示例6:创建实例并调用方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static void main (String[] args) throws Exception { Class<?> clazz = Person.class; Constructor<?> constructor = clazz.getConstructor(String.class, int .class); Object personInstance = constructor.newInstance("John" , 30 ); Method introduceMethod = clazz.getMethod("introduce" ); introduceMethod.invoke(personInstance); }
示例7:反射执行系统命令 1 2 3 4 5 6 7 8 9 10 11 public static void main (String[] args) throws Exception { 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" ); }
示例8:修改final字段 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 37 class Example { private final String message = "Initial Value" ; public String getMessage () { return message; } } public class ModifyFinalField { public static void main (String[] args) throws Exception { Example example = new Example (); System.out.println("Before modification: " + example.getMessage()); Field messageField = Example.class.getDeclaredField("message" ); messageField.setAccessible(true ); Field modifiersField = Field.class.getDeclaredField("modifiers" ); modifiersField.setAccessible(true ); modifiersField.setInt(messageField, messageField.getModifiers() & ~java.lang.reflect.Modifier.FINAL); messageField.set(example, "Modified Value" ); System.out.println("After modification: " + example.getMessage()); } }
总结 反射是Java的强大特性,它提供了运行时动态操作类的能力,是框架开发的基础。但反射也带来了性能开销和安全风险,需要谨慎使用。在实际开发中,应该:
理解反射的工作原理和应用场景
注意性能影响,合理优化
重视安全问题,做好防护措施
遵循最佳实践,编写健壮的代码