大家好!代理设计模式是 Java 中极具实用价值的设计模式,它通过"引入中间代理对象"的方式,在不修改目标对象代码的前提下实现功能增强,这在日志记录、事务控制、权限校验等场景中不可或缺。很多开发者容易混淆静态代理与动态代理的适用场景,也不清楚反射如何支撑动态代理的实现。
这篇文章会从代理模式的核心思想出发,拆解静态代理与动态代理的区别,深入讲解基于反射的 JDK 动态代理实现细节,帮你彻底吃透代理设计模式的核心知识点。
一、代理模式核心认知:先搞懂"为什么需要代理"
1.1 什么是代理模式?
代理模式(Proxy Pattern)是结构型设计模式的一种,核心思想是:为目标对象提供一个代理对象,由代理对象控制对目标对象的访问,并在访问前后添加额外功能。就像生活中的明星(客户端)不直接与节目(目标对象)打交道,而是通过经纪人(代理对象)完成议价、准备等操作。
在 Java 中,代理模式包含三个核心角色:
- 目标对象(Target) :被代理的核心对象,负责实现核心业务逻辑(如明星去实现"表演内容");
- 代理对象(Proxy) :持有目标对象的引用,对外提供与目标对象一致的接口,负责添加额外功能并调用目标对象方法(如经纪人和节目谈钱,做表演前的准备);
- 抽象接口(Subject) :目标对象和代理对象共同实现的接口,定义核心业务方法(如"表演"),保证代理对象与目标对象的一致性。
1.2 为什么要用代理模式?核心价值
代理模式的核心价值是"解耦核心逻辑与附加功能",具体体现在三个方面:
- 功能增强:在不修改目标对象代码的前提下,为目标方法添加日志、计时、权限校验等附加功能(符合"开闭原则");
- 控制访问:代理对象可控制对目标对象的访问时机、权限,比如拒绝未登录用户调用核心方法;
- 隐藏细节:代理对象可封装目标对象的复杂创建过程(如远程服务调用的连接建立),客户端只需与代理交互。
关键场景: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 核心原理
- 客户端调用
Proxy.newProxyInstance()方法,传入类加载器、目标接口数组和代理处理器; - JVM 在运行时基于目标接口动态生成代理类的字节码,并加载为 Class 对象;
- 代理类实例化时,将代理处理器传入,当客户端调用代理方法时,会触发处理器的
invoke()方法; - 在
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 高频面试题
- 代理模式的核心思想是什么?解决了什么问题? 答:核心思想是通过代理对象控制对目标对象的访问,在不修改目标对象代码的前提下添加功能。解决了"核心业务逻辑与附加功能耦合"的问题,符合开闭原则和单一职责原则。
- 静态代理和动态代理的区别?如何选择? 答:区别在于代理类的生成时机(编译期 vs 运行时)和灵活性。简单场景、目标类少且稳定时用静态代理;复杂场景、目标类多或频繁变动时用动态代理。有接口优先用 JDK 代理,无接口用 CGLIB 代理。
- Spring AOP 是如何选择代理方式的? 答:Spring AOP 默认优先使用 JDK 动态代理(目标类有接口时);若目标类无接口,则使用 CGLIB 代理。可通过配置
spring.aop.proxy-target-class=true强制使用 CGLIB 代理。 - 动态代理中,invoke 方法的三个参数分别是什么含义? 答:proxy 是动态生成的代理对象本身;method 是当前被调用的目标方法的 Method 对象;args 是目标方法的参数数组。
六、总结与延伸
6.1 核心总结
- 代理模式三角色:抽象接口(Subject)、目标对象(Target)、代理对象(Proxy);
- 静态代理:编译期生成代理类,简单但灵活性低,适合简单场景;
- 动态代理:运行时生成代理类,灵活性高,是框架核心技术(JDK 代理基于反射,CGLIB 基于字节码);
- 核心价值:解耦核心逻辑与附加功能,实现功能增强与控制访问。