JavaSE 进阶:代理设计模式核心知识点(静态代理 + 动态代理 + 反射实现 + 实战案例)

大家好!代理设计模式是 Java 中极具实用价值的设计模式,它通过"引入中间代理对象"的方式,在不修改目标对象代码的前提下实现功能增强,这在日志记录、事务控制、权限校验等场景中不可或缺。很多开发者容易混淆静态代理与动态代理的适用场景,也不清楚反射如何支撑动态代理的实现。

这篇文章会从代理模式的核心思想出发,拆解静态代理与动态代理的区别,深入讲解基于反射的 JDK 动态代理实现细节,帮你彻底吃透代理设计模式的核心知识点。

一、代理模式核心认知:先搞懂"为什么需要代理"

1.1 什么是代理模式?

代理模式(Proxy Pattern)是结构型设计模式的一种,核心思想是:为目标对象提供一个代理对象,由代理对象控制对目标对象的访问,并在访问前后添加额外功能。就像生活中的明星(客户端)不直接与节目(目标对象)打交道,而是通过经纪人(代理对象)完成议价、准备等操作。

在 Java 中,代理模式包含三个核心角色:

  • 目标对象(Target) :被代理的核心对象,负责实现核心业务逻辑(如明星去实现"表演内容");
  • 代理对象(Proxy) :持有目标对象的引用,对外提供与目标对象一致的接口,负责添加额外功能并调用目标对象方法(如经纪人和节目谈钱,做表演前的准备);
  • 抽象接口(Subject) :目标对象和代理对象共同实现的接口,定义核心业务方法(如"表演"),保证代理对象与目标对象的一致性。

1.2 为什么要用代理模式?核心价值

代理模式的核心价值是"解耦核心逻辑与附加功能",具体体现在三个方面:

  1. 功能增强:在不修改目标对象代码的前提下,为目标方法添加日志、计时、权限校验等附加功能(符合"开闭原则");
  2. 控制访问:代理对象可控制对目标对象的访问时机、权限,比如拒绝未登录用户调用核心方法;
  3. 隐藏细节:代理对象可封装目标对象的复杂创建过程(如远程服务调用的连接建立),客户端只需与代理交互。

关键场景:Spring AOP 的核心就是动态代理,事务管理、日志切面等都是通过代理实现;RPC 框架中,远程服务的本地代理也依赖此模式。

1.3 代理模式的分类:静态 vs 动态

根据代理对象的创建时机,代理模式分为静态代理和动态代理两类,核心区别在于"代理类是否在编译期生成":

对比维度 静态代理 动态代理
代理类生成时机 编译期手动编写或通过工具生成 运行时通过反射或字节码技术动态生成
代码灵活性 低,一个代理类通常对应一个目标类 高,一个代理处理器可代理多个目标类
维护成本 高,目标类接口修改时需同步修改代理类 低,无需手动维护代理类代码
核心技术 面向接口编程 反射(JDK 代理)、字节码修改(CGLIB)

二、静态代理:入门级实现,理解核心流程

静态代理是代理模式的基础实现,代理类在编译期就已确定,适合简单场景。下面以"明星表演"为例,完整实现静态代理。

2.1 实现步骤:三步完成静态代理

2.1.1 步骤1:定义抽象接口(Subject) :统一目标对象和代理对象的方法

Java 复制代码
public interface Star {
    /**
     * 实现唱歌的方法
     */
    void sing();

    /**
     * 实现跳舞的方法
     */
    void dance();

}

2.1.2 步骤2:实现目标对象(Target) :专注核心业务逻辑

Java 复制代码
public class XXXStar implements Star{

    /**
     * 实现唱歌的方法
     */
    @Override
    public void sing() {
        System.out.println("xxx在唱歌");
    }

    /**
     * 实现跳舞的方法
     */
    @Override
    public void dance() {
        System.out.println("xxx在跳舞");
    }
}

2.1.3 步骤3:实现代理对象(Proxy) :持有目标对象引用,添加额外功能

Java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class JJRProxy implements Star{

    //代理类要求:定义目标对象成员变量
    private Star star;
    /**
     * 实现唱歌的方法
     */
    @Override
    public void sing() {
        System.out.println("经纪人准备话筒,收钱");
        //让目标对象去唱歌
        star.sing();
    }

    /**
     * 实现跳舞的方法
     */
    @Override
    public void dance() {
        System.out.println("经纪人准备场地,收钱");
        //让目标对象去跳舞
        star.dance();
    }
}

2.1.4 静态代理测试

Java 复制代码
// 测试类
public class Demo01 {
    public static void main(String[] args) {
        //目标:创建目标、代理对象完成具体功能调用

        //1.创建目标对象
        Star xxxStar = new XXXStar();

        //2.创建代理对象
        JJRProxy proxy = new JJRProxy(xxxStar);

        //3.执行代理对象方法
        proxy.sing();
        proxy.dance();
    }
}

执行结果

复制代码
经纪人准备话筒,收钱
XXX在唱歌
经纪人准备场地,收钱
XXX在跳舞

2.2 静态代理核心总结:

  • 优点:实现简单,逻辑清晰,无需依赖复杂技术;
  • 缺点:代码冗余 (每个目标类需对应一个代理类)、维护成本高(接口新增方法时,目标类和代理类需同步修改)。

静态代理的局限性:当系统中有 100 个服务类需要添加日志功能时,就需要编写 100 个代理类,这显然不符合"高内聚低耦合"的设计原则------动态代理正是为解决这个问题而生。

三、动态代理:反射驱动,实现"一代理多用"

动态代理的核心是"运行时动态生成代理类",无需手动编写代理代码,一个代理处理器就能为多个目标类提供增强功能。

3.1 JDK 动态代理:基于反射的核心实现

JDK 动态代理是 Java 原生支持的代理方式,核心依赖 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口,必须基于接口实现

3.1.1 核心原理

  1. 客户端调用 Proxy.newProxyInstance() 方法,传入类加载器、目标接口数组和代理处理器;
  2. JVM 在运行时基于目标接口动态生成代理类的字节码,并加载为 Class 对象;
  3. 代理类实例化时,将代理处理器传入,当客户端调用代理方法时,会触发处理器的 invoke() 方法;
  4. invoke() 方法中,通过反射调用目标对象的核心方法,并添加附加功能。

3.1.2 jdk动态创建代理对象语法

类:java.lang.reflect.Proxy类

方法: public static Object newProxyInstance(ClassLoader loader, Class\[\] interfaces, InvocationHandler h)

  • 参数一:用于指定用哪个类加载器(推荐使用目标对象类加载器),去加载生成的代理类(生成字节码文件)
  • 参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法(要求与目标类实现一样的接口)
  • 参数三:用来指定生成的代理对象要干什么事情(代理要做的事情:自己的代理职责和调用目标对象的方法)

3.2 实现步骤:三步完成动态代理

下面以"明星表演"为例,完整实现静态代理。

3.2.1 步骤1:定义抽象接口(Subject) :同静态代理

Java 复制代码
public interface Star {
    /**
     * 实现唱歌的方法
     */
    void sing();

    /**
     * 实现跳舞的方法
     */
    void dance();

}

3.2.2 步骤2:实现目标对象(Target) :同静态代理

Java 复制代码
public class XXXStar implements Star{

    /**
     * 实现唱歌的方法
     */
    @Override
    public void sing() {
        System.out.println("xxx在唱歌");
    }

    /**
     * 实现跳舞的方法
     */
    @Override
    public void dance() {
        System.out.println("xxx在跳舞");
    }
}

3.2.3 步骤3:实现代理对象(Proxy) :持有目标对象引用,添加额外功能

Java 复制代码
public class Demo01 {
    public static void main(String[] args) {
        //目标:创建目标、代理对象完成具体功能调用

        //1.创建目标对象
        Star xxxStar = new XXXStar();

        //2.创建代理对象
        Star proxy = (Star) Proxy.newProxyInstance(
                xxxStar.getClass().getClassLoader(),
                xxxStar.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //在这里完成代理类要做的事情
                        //参数1:proxy,就是生成的代理对象,这里不需要。
                        //参数2:method,是反射方法对象(接口中的方法,具体是哪一个由外部调用决定,外部代理对象调用sing(),Method就是sing())
                        //参数3:args,调用方法传入的参数(外部代理对象调用方法(参数),传入参数是什么就是什么)

                        //代理对象需要做的职责
                        if (method.getName().equals("sing")) {
                            System.out.println("经纪人准备话筒,收钱");
                        } else if (method.getName().equals("dance")) {
                            System.out.println("经纪人准备场地,收钱");
                        }

                        //反射调用目标对象方法,就是执行method
                        //  Object result = method.invoke(目标对象, args);
                        Object result = method.invoke(xxxStar, args);

                        return result;
                    }
                }
        );


        //JJRProxy proxy = new JJRProxy(zrnStar);

        //3.执行代理对象方法
        proxy.sing();
        proxy.dance();
        System.out.println(proxy.getClass().getName());
    }
}

执行结果

复制代码
经纪人准备话筒,收钱
XXX在唱歌
经纪人准备场地,收钱
XXX在跳舞

四、代理模式实战:

动态代理的核心是"运行时动态生成代理类",无需手动编写代理代码,一个代理处理器就能为多个目标类提供增强功能。

4.1 实现步骤

4.1.1 步骤1:定义抽象接口(Subject)

Java 复制代码
public interface DataOperator {
    /**
     * 添加
     */
    void add();

    /**
     * 删除
     */
    void Delete();
}

4.1.2 步骤2:实现目标对象(Target)

Java 复制代码
public class UserManager implements DataOperator{
    /**
     * 添加
     */
    @Override
    public void add() {
        System.out.println("用户新增操作。。。");
        try {
            Thread.sleep((int)(3000*Math.random()));
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 删除
     */
    @Override
    public void Delete() {
        System.out.println("用户删除操作。。。");
        try {
            Thread.sleep((int)(3000*Math.random()));
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
Java 复制代码
public class DeptManager implements DataOperator{
    /**
     * 添加
     */
    @Override
    public void add() {
        System.out.println("部门新增操作。。。");
        try {
            Thread.sleep((int)(3000*Math.random()));
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 删除
     */
    @Override
    public void Delete() {
        System.out.println("部门删除操作。。。");
        try {
            Thread.sleep((int)(3000*Math.random()));
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

4.1.3 步骤3:实现代理对象(Proxy) :持有目标对象引用,添加额外功能

Java 复制代码
//用于创建代理对象的工厂工具类
public class TimeOutProxyFactoryUtil {
    public static  T createProxyObj(T target){
        return (T) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //代理对象需要做的职责: 统计每个目标对象方法执行耗时时间
                        //在目标方法执行前定义存储开始毫秒数
                        long start = System.currentTimeMillis();

                        //调用目标对象方法,就是执行method
                        Object result = method.invoke(target, args);

                        //在目标方法执行后定义存储结束毫秒数
                        Long end = System.currentTimeMillis();

                        System.out.printf("[%s]执行耗时毫秒数:%d%n", target.getClass().getName() + "." + method.getName() + "方法", end - start);

                        return result;
                    }
                }
        );
    }
}

4.1.4 测试

Java 复制代码
public class Demo01 {
    public static void main(String[] args) {
        //目标:使用动态代理给每个管理类统计每个方法运行耗时
        //疑问:静态代理和动态代理推荐哪个?
        //  答:如果只是对某一个目标进行代理哪个都可以,但是如果对多个目标对象进行相同的职责开发推荐使用动态代理。

        //1.创建目标对象-用户管理对象
        DataOperator userManager = new UserManager();

        //2.创建目标对象-部门管理对象
        DataOperator deptManager = new DeptManager();

        //3.生成用户管理对象的代理对象并调用方法
        DataOperator proxyObj1 = TimeOutProxyFactoryUtil.createProxyObj(userManager);
        proxyObj1.add();
        proxyObj1.Delete();

        //4.生成部门管理对象的代理对象并调用方法
        DataOperator proxyObj2 = TimeOutProxyFactoryUtil.createProxyObj(deptManager);
        proxyObj2.add();
        proxyObj2.Delete();
    }
}

执行结果

csharp 复制代码
用户新增操作。。。
[UserManager.add方法]执行耗时毫秒数:220
用户删除操作。。。
[UserManager.Delete方法]执行耗时毫秒数:1050
部门新增操作。。。
[DeptManager.add方法]执行耗时毫秒数:1967
部门删除操作。。。
[DeptManager.Delete方法]执行耗时毫秒数:673

五、面试/踩坑高频点(加分项)

5.1 常见踩坑点

  • JDK 代理误用于无接口类 :JDK 动态代理必须基于接口,若目标类无接口,会抛 ClassCastException,此时需改用 CGLIB;
  • CGLIB 代理无法代理 final 类/方法:CGLIB 通过继承实现代理,final 类无法被继承,final 方法无法被重写;
  • 动态代理对象类型判断错误:代理对象是 JVM 动态生成的类实例,不能直接强转为目标类类型(如 UserProxy 不能强转为 UserServiceImpl),只能强转为接口类型;
  • Spring AOP 切面不生效:检查是否加了 @Aspect 和 @Component 注解、切入点表达式是否正确、目标类是否被 Spring 管理(是否加了 @Service 等注解)。

5.2 高频面试题

  1. 代理模式的核心思想是什么?解决了什么问题? 答:核心思想是通过代理对象控制对目标对象的访问,在不修改目标对象代码的前提下添加功能。解决了"核心业务逻辑与附加功能耦合"的问题,符合开闭原则和单一职责原则。
  2. 静态代理和动态代理的区别?如何选择? 答:区别在于代理类的生成时机(编译期 vs 运行时)和灵活性。简单场景、目标类少且稳定时用静态代理;复杂场景、目标类多或频繁变动时用动态代理。有接口优先用 JDK 代理,无接口用 CGLIB 代理。
  3. Spring AOP 是如何选择代理方式的? 答:Spring AOP 默认优先使用 JDK 动态代理(目标类有接口时);若目标类无接口,则使用 CGLIB 代理。可通过配置 spring.aop.proxy-target-class=true 强制使用 CGLIB 代理。
  4. 动态代理中,invoke 方法的三个参数分别是什么含义? 答:proxy 是动态生成的代理对象本身;method 是当前被调用的目标方法的 Method 对象;args 是目标方法的参数数组。

六、总结与延伸

6.1 核心总结

  • 代理模式三角色:抽象接口(Subject)、目标对象(Target)、代理对象(Proxy);
  • 静态代理:编译期生成代理类,简单但灵活性低,适合简单场景;
  • 动态代理:运行时生成代理类,灵活性高,是框架核心技术(JDK 代理基于反射,CGLIB 基于字节码);
  • 核心价值:解耦核心逻辑与附加功能,实现功能增强与控制访问。
相关推荐
AI多Agent协作实战派17 小时前
AI多Agent协作系统实战(十七):凌晨4点,我的AI系统在“假装工作“——3个bug同时爆炸的5小时
java·前端·bug
gaolei_eit17 小时前
Java+Ai+vue
java·spring·maven
qq_25183645717 小时前
基于java Web 动漫视频网站毕业论文
java·开发语言·前端
LayZhangStrive17 小时前
JUC相关的函数、注解、变量杂记
java·面试·多线程·juc
未秃头的程序猿17 小时前
给公司做了个AI客服Agent,用的Spring AI 1.0,3天上线领导拍板了
java·后端·ai编程
曹牧17 小时前
Eclipse 批量文本替换
java·ide·eclipse
程序员清风17 小时前
OpenAI官方发布最新提示词技巧!
java·后端·面试
一只枫林17 小时前
MySQL内、外连接知识点汇总
java·前端·数据库
梅头脑17 小时前
写了3年CRUD,volatile和synchronized的区别还是答不清——直到我画出了这张三性两锁边界图
java
暮暮祈安17 小时前
Celery 新手入门指南
java·数据库·python·flask·httpx