摘要:本文介绍设计模式中的代理模式,核心是通过代理在不修改真实对象代码的前提下实现功能增强。含**静态代理、动态代理(JDK 动态代理,CGLIB 动态代理)**实现方式。最后还对比了三种代理的实现基础、限制、性能与适用场景。


思维导图


1. 代理模式
1.1 概述
代理模式是一种旨在控制目标对象访问的设计模式:当外部访问对象因目标对象存在访问限制、需附加额外逻辑(如日志记录、性能监控)等原因,不适合或无法直接引用目标对象时,会通过一个 "代理对象" 作为中介,间接实现对目标对象的交互。
在 Java 技术体系中,代理的实现可根据 "代理类生成时机" 分为两大类别:
1. 静态代理: 代理类的代码在项目编译阶段就已生成,与目标类的字节码一同存在于最终产物中;**2. 动态代理:**代理类并非提前编译好,而是在 Java 程序运行过程中,根据实际业务需求动态创建并加载,无需手动编写代理类代码。
1.2 结构
代理模式分为三种角色:
-
抽象主题类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
-
真实主题类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终对象。
-
代理类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
1.3 静态代理
**例】火车站卖票:**如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了。这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象。

代码如下:
md-end-block
//卖票接口
public interface SellTickets {
void sell();
}
//火车站 火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {
public void sell() {
System.out.println("火车站卖票");
}
}
//代售点
public class ProxyPoint implements SellTickets {
private TrainStation station = new TrainStation();
public void sell() {
System.out.println("代理点收取一些服务费用");
station.sell();
}
}
//测试类
public class Client {
public static void main(String[] args) {
ProxyPoint pp = new ProxyPoint();
pp.sell();
}
}
可以看出测试类直接访问的是ProxyPoint类对象,也就是说ProxyPoint作为访问对象和目标对象的中介。同时也对sell方法进行了增强(代理点收取一些服务费用)。
1.4 JDK动态代理
接下来我们使用动态代理实现上面案例,先说说JDK提供的动态代理。Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。
代码如下:
md-end-block
//卖票接口
public interface SellTickets {
void sell();
}
//火车站 火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {
public void sell() {
System.out.println("火车站卖票");
}
}
//代理工厂,用来创建代理对象
public class ProxyFactory {
private TrainStation station = new TrainStation();
public SellTickets getProxyObject() {
//使用Proxy获取代理对象
/*
newProxyInstance()方法参数说明:
ClassLoader loader : 类加载器,用于加载代理类,使用真实对象的类加载器即可
Class<?>[] interfaces : 真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口
InvocationHandler h : 代理对象的调用处理程序
*/
SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),
station.getClass().getInterfaces(),
new InvocationHandler() {
/*
InvocationHandler中invoke方法参数说明:
proxy : 代理对象
method : 对应于在代理对象上调用的接口方法的 Method 实例
args : 代理对象调用接口方法时传递的实际参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理点收取一些服务费用(JDK动态代理方式)");
//执行真实对象
Object result = method.invoke(station, args);
return result;
}
});
return sellTickets;
}
}
//测试类
public class Client {
public static void main(String[] args) {
//获取代理对象
ProxyFactory factory = new ProxyFactory();
SellTickets proxyObject = factory.getProxyObject();
proxyObject.sell();
}
}
核心角色说明
(1)
SellTickets
接口
- 这是一个功能接口,定义了 "卖票" 的规范(只有
sell()
方法)。- 作用:作为真实对象 和代理对象的共同接口,保证代理对象能以与真实对象相同的方式被调用(符合 "里氏替换原则")。
(2)
TrainStation
类(真实主题)
- 实现了
SellTickets
接口,是实际执行业务逻辑的类。sell()
方法的实现是 "火车站卖票" 的核心功能(打印 "火车站卖票")。- 角色:被代理的对象,专注于核心业务逻辑。
(3)
ProxyFactory
类(代理工厂)
- 用于创建代理对象 的工厂类,内部持有一个真实对象(
TrainStation
实例)。- 核心方法
getProxyObject()
通过Proxy.newProxyInstance()
生成代理对象,需要 3 个参数:
ClassLoader loader
:类加载器,直接使用真实对象的类加载器。Class<?>[] interfaces
:真实对象实现的接口。InvocationHandler h
:代理对象的 "方法调用处理器",是实现功能增强的核心。(4)
InvocationHandler
接口(方法调用处理器)
- 这是一个匿名内部类实现,定义了代理对象调用方法时的 "增强逻辑"。
- 核心方法
invoke()
在代理对象调用任何方法时都会被自动触发,包含 3 个参数:
proxy
:当前的代理对象(一般不用)。method
:被调用的方法(这里是sell()
方法的Method
实例)。args
:调用方法时传递的参数(这里sell()
无参数,所以为null
)。- 逻辑: 先执行增强操作,再通过
method.invoke(station, args)
调用真实对象的sell()
方法,实现 "增强 + 核心业务" 的组合执行。
1.5 CGLIB动态代理
当没有定义 SellTickets 接口,仅存在 TrainStation 类时,由于 JDK 动态代理依赖接口实现,无法直接使用,此时可采用 CGLIB 代理来解决。
CGLIB 作为一个高性能的代码生成包,能为没有实现接口的类创建代理,它通过继承目标类(TrainStation)生成代理子类的方式实现功能增强,很好地补充了 JDK 动态代理在无接口场景下的不足,从而实现在不修改 TrainStation 类代码的前提下,对其卖票功能进行扩展(如添加服务费用收取等操作)。
引入依赖:
md-end-block
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
代码如下:
md-end-block
//火车站
public class TrainStation {
public void sell() {
System.out.println("火车站卖票");
}
}
//代理工厂
public class ProxyFactory implements MethodInterceptor {
private TrainStation target = new TrainStation();
public TrainStation getProxyObject() {
//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
Enhancer enhancer =new Enhancer();
//设置父类的字节码对象
enhancer.setSuperclass(target.getClass());
//设置回调函数
enhancer.setCallback(this);
//创建代理对象
TrainStation obj = (TrainStation) enhancer.create();
return obj;
}
/*
intercept方法参数说明:
o : 代理对象
method : 真实对象中的方法的Method实例
args : 实际参数
methodProxy :代理对象中的方法的method实例
*/
public TrainStation intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)");
TrainStation result = (TrainStation) methodProxy.invokeSuper(o, args);
return result;
}
}
//测试类
public class Client {
public static void main(String[] args) {
//创建代理工厂对象
ProxyFactory factory = new ProxyFactory();
//获取代理对象
TrainStation proxyObject = factory.getProxyObject();
proxyObject.sell();
}
}
1.6 三种代理的对比
JDK 动态代理 vs CGLIB 代理
- 实现基础:JDK 动态代理基于接口实现,要求目标类必须实现接口,代理对象是接口的实现类;CGLIB 代理基于继承实现,无需目标类实现接口,通过生成目标类的子类作为代理类。
- 限制:JDK 代理仅能代理接口中的方法;CGLIB 不能代理 final 类或 final 方法。
- 性能:JDK 1.6 及之前,CGLIB 因基于字节码生成,效率高于 JDK 代理;JDK 1.8 优化后,JDK 代理在多数场景(调用次数较少时)效率更高,仅在大量调用时与 CGLIB 接近或略优。
- 使用场景 :有接口时优先用 JDK 代理,无接口时只能用 CGLIB 代理。
动态代理(JDK/CGLIB)vs 静态代理
- 代码维护:静态代理需为每个接口方法编写代理逻辑,接口新增方法时,所有实现类和代理类都需修改,维护成本高;动态代理通过集中处理器(如 InvocationHandler)统一处理所有方法,接口变更时无需修改代理逻辑,灵活性更高。
- 生成时机:静态代理的代理类在编译期手动编写完成;动态代理的代理类在运行时动态生成。
- 适用场景:静态代理适用于接口方法少、变更少的简单场景;动态代理适用于接口方法多、频繁变更的复杂场景,可大幅减少重复代码。
综上,动态代理在灵活性和维护性上优于静态代理,而 JDK 代理与 CGLIB 代理的选择主要取决于是否有接口及 JDK 版本。
1.7 应用场景
静态代理 :代理类在编译期手动编写,与目标类实现同一接口,一对一代理。
常见场景:
- 简单功能增强 :如对单个类的特定方法添加日志、计时等简单增强(方法数量少且稳定)。
- 访问控制 :如对敏感接口(如支付接口)设置临时访问权限校验,代理类可在调用真实方法前拦截非法请求。
- 测试环境模拟 :在单元测试中,用静态代理模拟第三方服务(如模拟支付接口返回固定结果,避免真实调用)。
JDK 动态代理 :基于接口动态生成代理类,运行时创建,需目标类实现接口。
常见场景:
- 框架级通用增强:如 Spring AOP 的默认代理方式(对实现接口的 Bean),用于事务管理、日志记录、异常处理等横切逻辑。
- RPC 框架:如 Dubbo 中对服务接口的代理,通过代理类实现远程调用(序列化、网络传输等逻辑封装)。
- 权限校验:在分布式系统中,对接口统一添加权限拦截(如登录态校验、接口访问限流)。
- 数据源路由:在多数据源场景中,通过代理动态切换数据源(如读写分离,根据方法名路由到主库或从库)。
CGLIB 代理 :基于继承动态生成代理子类,无需目标类实现接口,可代理非 final 类 / 方法。
常见场景:
- 无接口类的增强:如对 POJO 类、工具类等未实现接口的类进行增强(如添加缓存逻辑)。
- 第三方库适配 :当依赖的第三方类未实现接口,但需要增强其方法时(如对第三方 HTTP 客户端添加超时重试逻辑)。
- Spring 框架补充:Spring 中对未实现接口的 Bean 默认使用 CGLIB 代理。
- ORM 框架:如 MyBatis 中对 Mapper 接口的代理(虽然 Mapper 是接口,但底层结合 CGLIB 实现动态 SQL 执行逻辑)。
大功告成!