【从零开始java学习|第三十篇】反射与动态代理 :框架的灵魂
目录
第二章:获取 Class 对象的三种方式 (拿到解剖许可证)
这是咱们 Java 基础(Java SE)的终极篇章。
如果说前面学的基础语法、面向对象、多线程,是教你如何一砖一瓦地盖房子;那么今天我们要学的反射(Reflection)和动态代理(Dynamic Proxy),就是直接赋予你“上帝视角”和“魔法”。
你是否好奇过:为什么 Spring 框架只需要你写一个全限定类名的配置,它就能自动帮你把对象创建出来?为什么 Mybatis 只需要你写个接口,它就能连上数据库帮你查数据?
这一切底层的高级黑魔法,全都在今天这篇博客里。系好安全带,大厂核心内功,发车!
第一章:反射的概述 (程序的 X 光机)
什么是反射? 在正常情况下,我们写代码都是先有类,然后 new 出对象,再用对象去调用方法。这叫“正射”。 而反射,允许你在程序运行期间(而不是编译期间),对任意一个类进行“解剖”。你可以获取这个类的所有信息(哪怕是 private 私有的构造方法、变量和成员方法),并且可以直接操作它们。
在大厂里有一句名言:反射是框架的灵魂。 没有反射,就没有今天极其繁荣的 Java 框架生态。它无视了 Java 面向对象中的“封装”特性,就像一台 X 光机,把类扒得底裤都不剩。
第二章:获取 Class 对象的三种方式 (拿到解剖许可证)
要对一个类进行反射解剖,第一步必须拿到这个类的字节码文件对象 (Class 对象)。这是所有反射操作的入口。
获取 Class 对象有三种经典姿势:
-
方式一:
Class.forName("全限定类名")-
最常用! 极其适合配合配置文件使用。只需要知道一个字符串的名字,就能把类加载进内存。
-
-
方式二:
类名.class-
常用于当做参数传递,比如锁对象
synchronized(Student.class),或者告诉某些方法你需要什么类型。
-
-
方式三:
对象.getClass()-
当你已经拿到了这个类的对象,想通过对象反向查它的类信息时使用。
-
注意:同一个类在 JVM 内存中,永远只有一个 Class 对象。以上三种方式拿到的,是同一个东西。

第三章:反射的“庖丁解牛” (解剖三大件)
拿到 Class 对象后,我们就可以提取里面的三大核心部件:构造方法(Constructor)、成员变量(Field)、成员方法(Method)。
核心心法:暴力破解 反射中最牛逼、也最危险的一个方法叫 setAccessible(true)。如果遇到 private 修饰的私有属性或方法,直接调用这个方法,就能强行破坏封装,直接访问!
1. 反射获取构造方法 (Constructor)
-
getConstructors():获取所有public修饰的构造方法。 -
getDeclaredConstructors():获取所有构造方法(包含 private)。 -
核心作用: 拿到构造器后,调用
newInstance()方法,就能像new关键字一样创建出对象。
2. 反射获取成员变量 (Field)
-
getDeclaredFields():获取所有的成员变量。 -
核心作用: 拿到变量对象后,可以通过
set(对象, 值)给变量强行赋值;通过get(对象)获取变量的值。
3. 反射获取成员方法 (Method)
-
getDeclaredMethods():获取本类中包含私有在内的所有成员方法。 -
核心作用: 拿到方法对象后,通过
invoke(对象, 参数)让这个方法运行起来。
第四章:综合练习 01 (保存任意对象数据)
需求: 给你任意一个对象,在不清楚对象字段的情况下,把对象内部所有的变量名和变量值保存到文件中去。这在做底层日志搜集系统时极其常见。
完整代码与操作说明
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.lang.reflect.Field;
public class ReflectSaveDemo {
public static void main(String[] args) throws Exception {
// 1. 准备两个完全不同的对象
Student stu = new Student("张三", 23, "男");
Teacher tea = new Teacher("李老师", 8000.5);
// 2. 调用我们写的万能保存方法
saveObject(stu);
saveObject(tea);
System.out.println("数据保存成功,请查看项目根目录的 obj_data.txt");
}
// ★ 核心黑魔法:接收任意类型的对象
public static void saveObject(Object obj) throws Exception {
// 1. 获取字节码文件对象 (入口)
Class clazz = obj.getClass();
BufferedWriter bw = new BufferedWriter(new FileWriter("obj_data.txt", true));
bw.write("======== 正在保存 " + clazz.getSimpleName() + " 类的数据 ========");
bw.newLine();
// 2. 获取该类的所有成员变量
Field[] fields = clazz.getDeclaredFields();
// 3. 遍历变量,提取名称和值
for (Field field : fields) {
// 暴力破解!无视 private 封装
field.setAccessible(true);
// 获取变量的名字
String name = field.getName();
// 获取这个变量在当前传入的 obj 对象中的实际值
Object value = field.get(obj);
bw.write(name + " = " + value);
bw.newLine();
}
bw.close();
}
}
// 测试用的实体类 (内部私有属性,只有全参构造,连 Getter 都没有)
class Student {
private String name;
private int age;
private String gender;
public Student(String name, int age, String gender) {
this.name = name; this.age = age; this.gender = gender;
}
}
class Teacher {
private String name;
private double salary;
public Teacher(String name, double salary) {
this.name = name; this.salary = salary;
}
}
操作说明:
-
新建
ReflectSaveDemo.java,将上述代码粘贴并保存。 -
运行
main方法。 -
刷新项目根目录,会生成一个
obj_data.txt。打开你会发现,即使Student和Teacher类的属性全是private,数据也被扒得清清楚楚。
第五章:综合练习 02 (利用反射动态创建对象并运行方法)
需求: 模拟 Spring 等框架的底层逻辑。不要把类的创建写死在代码里,而是根据配置文件,动态决定运行哪个类的哪个方法。
完整代码与操作说明
import java.io.FileReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Properties;
public class FrameworkDemo {
public static void main(String[] args) throws Exception {
// 1. 读取配置文件 (解耦的核心!)
Properties prop = new Properties();
prop.load(new FileReader("config.properties"));
// 获取类名和方法名
String className = prop.getProperty("className");
String methodName = prop.getProperty("methodName");
// 2. 反射三步曲开始
// 第一步:加载类进内存,获取 Class 对象
Class clazz = Class.forName(className);
// 第二步:获取无参构造方法,并实例化对象
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Object obj = constructor.newInstance();
// 第三步:获取指定名称的方法,并运行
Method method = clazz.getDeclaredMethod(methodName);
method.setAccessible(true);
// invoke 意思是让 obj 这个对象,去执行 method 这个方法
method.invoke(obj);
}
}
// 供配置文件调用的测试类
class Airplane {
public void fly() {
System.out.println("飞机正在万米高空翱翔...");
}
}
class Rocket {
public void launch() {
System.out.println("火箭点火,发射升空!");
}
}
操作说明:
-
在项目根目录下新建一个文本文件,命名为
config.properties。 -
在文件里输入(不能有空格):
PropertiesclassName=Airplane methodName=fly -
新建
FrameworkDemo.java,复制上面代码。 -
运行
main方法,控制台会打印飞机起飞。 -
见证奇迹: 修改
config.properties,改成className=Rocket和methodName=launch。不修改一行 Java 代码,再次运行程序,输出直接变成了火箭发射!这就是反射带来的极度灵活性。

第六章:动态代理 (无侵入式的终极增强)
了解了反射,接下来就是 Java 高级特性的究极体:动态代理 (Dynamic Proxy)。
思想分析:经纪人模式 假设有一位大明星叫“鸡哥”,他会唱歌和跳舞(核心业务代码)。 现在要开演唱会了,除了唱歌跳舞,还需要:包场、收钱、应付狗仔队。如果让鸡哥自己干这些,他会累死,代码也会极其臃肿(核心业务与非核心业务耦合)。
解决方案: 给鸡哥找一个“经纪人(代理对象)”。 外面的人想听唱歌,不能直接找鸡哥,必须先找经纪人。经纪人把钱收了,场地布置好,然后再把麦克风递给鸡哥让他唱(通过反射调用目标方法)。唱完后,经纪人再负责善后。
这就是AOP(面向切面编程)的底层逻辑:在不修改原对象代码的情况下,动态地对它的功能进行增强!
第七章:动态代理的代码实现
Java 官方在 java.lang.reflect.Proxy 类中提供了一个方法 newProxyInstance(),专门用来在运行时动态生成代理对象。
完整代码与操作说明
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyDemo {
public static void main(String[] args) {
// 1. 创建真正的大明星对象
BigStar jiGe = new BigStar("鸡哥");
// 2. 找代理公司,为鸡哥创建一个经纪人 (代理对象)
Star proxy = ProxyUtil.createProxy(jiGe);
// 3. 调用代理对象的方法 (看似调的是唱歌,其实中间经过了经纪人的拦截)
String result = proxy.sing("《只因你太美》");
System.out.println("粉丝评价: " + result);
}
}
// 铁律一:JDK 动态代理必须有接口
interface Star {
String sing(String name);
void dance();
}
// 目标类:真正干活的大明星
class BigStar implements Star {
private String name;
public BigStar(String name) { this.name = name; }
@Override
public String sing(String name) {
System.out.println(this.name + " 正在深情演唱:" + name);
return "太好听了吧!";
}
@Override
public void dance() {
System.out.println(this.name + " 正在表演铁山靠");
}
}
// 核心:代理工具类
class ProxyUtil {
public static Star createProxy(BigStar bigStar) {
// 使用 Proxy 类的静态方法生成代理对象
Star proxy = (Star) Proxy.newProxyInstance(
// 参数一:类加载器,用于加载动态生成的代理类字节码。通常就填当前类的加载器即可。
ProxyUtil.class.getClassLoader(),
// 参数二:接口数组,告诉代理对象要代理哪些方法(明星能干啥,经纪人就要能接啥活)。
new Class[]{Star.class},
// 参数三:InvocationHandler (调用处理器),用于定义代理对象收到请求后具体该怎么干活。
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 经纪人的增强逻辑:如果是唱歌,先收钱
if ("sing".equals(method.getName())) {
System.out.println("【经纪人拦截】演唱会准备,先收出场费 100 万!");
} else if ("dance".equals(method.getName())) {
System.out.println("【经纪人拦截】准备跳舞场地,收 50 万!");
}
// 核心代码:通过反射,让真正的大明星去执行原本的方法
// args 就是调用方法时传进来的参数,比如歌名
Object result = method.invoke(bigStar, args);
// 经纪人的善后逻辑
System.out.println("【经纪人善后】演出结束,安排保安护送明星退场。");
// 返回明星原方法的执行结果
return result;
}
}
);
return proxy;
}
}
操作说明:
-
新建
ProxyDemo.java,将上面的代码统统粘贴进去保存。 -
运行
main方法。 -
仔细观察控制台的打印顺序,你会发现原本极其单纯的
sing()方法前后,都被自动织入了收钱和保安退场的逻辑,而BigStar的原代码连一个标点符号都没有修改!

第八章:面试高频轰炸区
-
Q:反射的执行效率是不是很低?
-
A: 是的。因为反射需要动态解析类,跳过了 JVM 的许多编译期优化,而且涉及到安全检查机制,所以比普通的
new对象并调用要慢。但在日常业务或框架启动阶段,这点性能损耗微乎其微,它带来的灵活性收益远大于性能代价。
-
-
Q:说说
setAccessible(true)的作用?-
A: 它是反射的核心破壁人,叫做“暴力反射”。它取消了 Java 语言访问检查,使得我们可以访问类的私有(
private)成员。
-
-
Q:JDK 动态代理有什么致命限制?还有别的方案吗?
-
A: JDK 自带的动态代理(也就是我们在代码里写的
Proxy.newProxyInstance),被代理的目标类必须至少实现一个接口!如果一个类没有接口,JDK 代理就歇菜了。 -
扩展: 在大厂的 Spring 框架中,如果类实现了接口,默认使用 JDK 动态代理;如果类没有实现接口,Spring 会自动切换到 CGLIB 动态代理(底层是通过生成目标类的子类来实现代理的)。
-
恭喜你,各位少侠!历经三十篇博客,从懵懵懂懂的 Hello World,到面向对象、集合框架、IO 流、多线程并发,再到如今触碰框架灵魂的反射与动态代理,你的 Java 基础(Java SE)已经大成。
如果我的内容对你有帮助,请点赞,评论,收藏。接下来我将继续更新相关内容!
更多推荐

所有评论(0)