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 基于字节码);
  • 核心价值:解耦核心逻辑与附加功能,实现功能增强与控制访问。
相关推荐
武子康2 小时前
Java-189 Guava Cache 源码剖析:LocalCache、Segment 与 LoadingCache 工作原理全解析
java·redis·后端·spring·缓存·guava·guava cache
程序员小假2 小时前
我们来说一说 Redis 主从复制的原理及作用
java·后端
木鹅.2 小时前
聊天记忆
java
我命由我123452 小时前
Java 开发使用 MyBatis PostgreSQL 问题:使用了特殊字符而没有正确转义
java·开发语言·数据库·postgresql·java-ee·mybatis·学习方法
源码获取_wx:Fegn08952 小时前
基于springboot + vue图书商城系统
java·vue.js·spring boot·后端·spring·课程设计
未秃头的程序猿2 小时前
解决ShardingSphere分片算法在Devtools热重启后SpringUtil.getBean()空指针问题
java·后端
better_liang2 小时前
每日Java面试场景题知识点之-RabbitMQ
java·消息队列·rabbitmq·面试题·异步通信·企业级开发·系统解耦
芒克芒克2 小时前
《Git分支实战:从创建到合并的全流程》
java·git
Chloeis Syntax2 小时前
MySQL初阶学习日记(5)--- 联合查询
java·笔记·学习·mysql