【 设计模式 | 结构型模式 代理模式 】
摘要:本文介绍设计模式中的代理模式,核心是通过代理在不修改真实对象代码的前提下实现功能增强。含静态代理、动态代理(JDK 动态代理,CGLIB 动态代理)实现方式。最后还对比了三种代理的实现基础、限制、性能与适用场景。
思维导图
1. 代理模式
1.1 概述
代理模式是一种旨在控制目标对象访问的设计模式:当外部访问对象因目标对象存在访问限制、需附加额外逻辑(如日志记录、性能监控)等原因,不适合或无法直接引用目标对象时,会通过一个 "代理对象" 作为中介,间接实现对目标对象的交互。
在 Java 技术体系中,代理的实现可根据 "代理类生成时机" 分为两大类别:
1. 静态代理:代理类的代码在项目编译阶段就已生成,与目标类的字节码一同存在于最终产物中;2. 动态代理:代理类并非提前编译好,而是在 Java 程序运行过程中,根据实际业务需求动态创建并加载,无需手动编写代理类代码。
1.2 结构
代理模式分为三种角色:
1. 抽象主题类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
2. 真实主题类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终对象。
3. 代理类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
1.3 静态代理
例】火车站卖票:如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了。这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象。
代码如下:
//卖票接口
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方法)来获取代理对象。
代码如下:
//卖票接口
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 类代码的前提下,对其卖票功能进行扩展(如添加服务费用收取等操作)。
引入依赖:
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>2.2.2</version>
</dependency>
代码如下:
//火车站
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 执行逻辑)。
大功告成!