目录

第一章:反射的概述 (程序的 X 光机)

第二章:获取 Class 对象的三种方式 (拿到解剖许可证)

第三章:反射的“庖丁解牛” (解剖三大件)

1. 反射获取构造方法 (Constructor)

2. 反射获取成员变量 (Field)

3. 反射获取成员方法 (Method)

第四章:综合练习 01 (保存任意对象数据)

完整代码与操作说明

第五章:综合练习 02 (利用反射动态创建对象并运行方法)

完整代码与操作说明

第六章:动态代理 (无侵入式的终极增强)

第七章:动态代理的代码实现

完整代码与操作说明

第八章:面试高频轰炸区


这是咱们 Java 基础(Java SE)的终极篇章

如果说前面学的基础语法、面向对象、多线程,是教你如何一砖一瓦地盖房子;那么今天我们要学的反射(Reflection)和动态代理(Dynamic Proxy),就是直接赋予你“上帝视角”和“魔法”。

你是否好奇过:为什么 Spring 框架只需要你写一个全限定类名的配置,它就能自动帮你把对象创建出来?为什么 Mybatis 只需要你写个接口,它就能连上数据库帮你查数据?

这一切底层的高级黑魔法,全都在今天这篇博客里。系好安全带,大厂核心内功,发车!


第一章:反射的概述 (程序的 X 光机)

什么是反射? 在正常情况下,我们写代码都是先有类,然后 new 出对象,再用对象去调用方法。这叫“正射”。 而反射,允许你在程序运行期间(而不是编译期间),对任意一个类进行“解剖”。你可以获取这个类的所有信息(哪怕是 private 私有的构造方法、变量和成员方法),并且可以直接操作它们。

在大厂里有一句名言:反射是框架的灵魂。 没有反射,就没有今天极其繁荣的 Java 框架生态。它无视了 Java 面向对象中的“封装”特性,就像一台 X 光机,把类扒得底裤都不剩。


第二章:获取 Class 对象的三种方式 (拿到解剖许可证)

要对一个类进行反射解剖,第一步必须拿到这个类的字节码文件对象 (Class 对象)。这是所有反射操作的入口。

获取 Class 对象有三种经典姿势:

  1. 方式一:Class.forName("全限定类名")

    • 最常用! 极其适合配合配置文件使用。只需要知道一个字符串的名字,就能把类加载进内存。

  2. 方式二:类名.class

    • 常用于当做参数传递,比如锁对象 synchronized(Student.class),或者告诉某些方法你需要什么类型。

  3. 方式三:对象.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;
    }
}

操作说明:

  1. 新建 ReflectSaveDemo.java,将上述代码粘贴并保存。

  2. 运行 main 方法。

  3. 刷新项目根目录,会生成一个 obj_data.txt。打开你会发现,即使 StudentTeacher 类的属性全是 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("火箭点火,发射升空!");
    }
}

操作说明:

  1. 在项目根目录下新建一个文本文件,命名为 config.properties

  2. 在文件里输入(不能有空格):

    Properties
    className=Airplane
    methodName=fly
    
  3. 新建 FrameworkDemo.java,复制上面代码。

  4. 运行 main 方法,控制台会打印飞机起飞。

  5. 见证奇迹: 修改 config.properties,改成 className=RocketmethodName=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;
    }
}

操作说明:

  1. 新建 ProxyDemo.java,将上面的代码统统粘贴进去保存。

  2. 运行 main 方法。

  3. 仔细观察控制台的打印顺序,你会发现原本极其单纯的 sing() 方法前后,都被自动织入了收钱和保安退场的逻辑,而 BigStar 的原代码连一个标点符号都没有修改!


第八章:面试高频轰炸区

  1. Q:反射的执行效率是不是很低?

    • A: 是的。因为反射需要动态解析类,跳过了 JVM 的许多编译期优化,而且涉及到安全检查机制,所以比普通的 new 对象并调用要慢。但在日常业务或框架启动阶段,这点性能损耗微乎其微,它带来的灵活性收益远大于性能代价。

  2. Q:说说 setAccessible(true) 的作用?

    • A: 它是反射的核心破壁人,叫做“暴力反射”。它取消了 Java 语言访问检查,使得我们可以访问类的私有(private)成员。

  3. Q:JDK 动态代理有什么致命限制?还有别的方案吗?

    • A: JDK 自带的动态代理(也就是我们在代码里写的 Proxy.newProxyInstance),被代理的目标类必须至少实现一个接口!如果一个类没有接口,JDK 代理就歇菜了。

    • 扩展: 在大厂的 Spring 框架中,如果类实现了接口,默认使用 JDK 动态代理;如果类没有实现接口,Spring 会自动切换到 CGLIB 动态代理(底层是通过生成目标类的子类来实现代理的)。


恭喜你,各位少侠!历经三十篇博客,从懵懵懂懂的 Hello World,到面向对象、集合框架、IO 流、多线程并发,再到如今触碰框架灵魂的反射与动态代理,你的 Java 基础(Java SE)已经大成。

如果我的内容对你有帮助,请点赞,评论,收藏。接下来我将继续更新相关内容!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐