一、什么是代理模式?
代理模式(Proxy Pattern)是一种结构型设计模式 ,其核心思想是:
通过引入一个代理对象作为中间层,控制对目标对象(真实对象)的访问,并在访问前后添加额外的处理逻辑。
代理模式的本质是 "接力调用",代理对象和目标对象实现相同的接口,对外提供一致的访问方式,但代理对象会在调用目标对象的方法前后 "插队" 执行自己的逻辑。
二、代理模式的核心角色
代理模式涉及三个核心角色:
- 抽象主题(Subject):定义代理和目标对象的公共接口,确保两者具有相同的行为。
- 真实主题(Real Subject):实际处理业务逻辑的目标对象,如 "明星"。
- 代理主题(Proxy Subject):持有真实主题的引用,在调用真实主题的方法前后添加额外逻辑,如 "经纪人"。
三、案例场景:明星与经纪人
我们以 "明星表演" 为例:
- 真实主题:明星(ConcreteStar)负责核心表演逻辑。
- 代理主题:经纪人(StarAgentProxy)作为代理,在明星表演前后处理事务(如安排场地、结算费用)。
- 抽象主题:定义 "表演" 接口(Star),约束明星和经纪人的共同行为。
四、代码实现(Java 静态代理)
1. 抽象主题接口(Star)
// 定义明星的公共行为:表演
interface Star {
void perform(); // 表演方法
}
2. 真实主题:具体明星(ConcreteStar)
// 真实明星类,实现核心表演逻辑
class ConcreteStar implements Star {
@Override
public void perform() {
System.out.println("明星开始精彩的表演!"); // 核心业务逻辑
}
}
3. 代理主题:经纪人(StarAgentProxy)
// 经纪人代理类,实现Star接口并持有明星引用
class StarAgentProxy implements Star {
private final Star realStar; // 让经纪人代理类持有目标对象(真实明星)的引用
//这样在下面就可以调用真实明星的方法
// 构造方法:接收真实明星对象
public StarAgentProxy(Star realStar) {
this.realStar = realStar;
}
@Override
public void perform() {
// 前置处理:代理在调用真实对象前的逻辑
System.out.println("经纪人:安排场地、宣传推广、对接流程...");
// 调用真实明星的表演方法
realStar.perform(); // 核心逻辑由真实对象处理
// 后置处理:代理在调用真实对象后的逻辑
System.out.println("经纪人:处理费用结算、安排下一场行程...");
}
}
4. 客户端测试(StaticProxyTest)
public class StaticProxyTest {
public static void main(String[] args) {
// 创建真实明星对象
Star realStar = new ConcreteStar();
// 创建经纪人代理对象,传入真实明星
Star agentProxy = new StarAgentProxy(realStar);
// 客户端通过代理对象调用方法(仿佛直接调用真实对象)
agentProxy.perform();
}
}
运行结果:

5.变量引用关系
在代码 Star agentProxy = new StarAgentProxy(realStar);
里:
realStar
是ConcreteStar
类的一个实例,也就是目标对象。agentProxy
是StarAgentProxy
类的一个实例,即代理对象。
在 StarAgentProxy
类的构造方法 public StarAgentProxy(Star star) { this.star = star; }
中,只是把 realStar
对象的引用传递给了 StarAgentProxy
类的成员变量 star
,这意味着 StarAgentProxy
类的实例 agentProxy
持有了 realStar
对象的引用,而并非 agentProxy
本身就是 realStar
对象。
当执行 agentProxy.perform();
时,由于 agentProxy
是 StarAgentProxy
类的实例,所以调用的是 StarAgentProxy
类中重写的 perform()
方法。
五、代理模式的核心原理
- 接口约束 :代理和真实对象实现相同接口,确保客户端可以像使用真实对象一样使用代理对象(透明性)。
- 组合关系 :代理对象通过构造方法接收真实对象(
has-a
关系),而非继承(is-a
),降低耦合度。- 责任分离 :
- 真实对象专注核心业务(如 "表演"),
- 代理对象专注辅助逻辑(如 "预处理" 和 "后处理")。
六、继承实现代理的三大致命缺点
// 明星接口(抽象主题)
interface Star {
void perform(); // 表演方法
}
// 真实明星(目标对象)
class ConcreteStar implements Star {
@Override public void perform() {
System.out.println("明星开始精彩的表演!");
}
}
// 经纪人代理类(继承真实明星)
class StarAgentProxy extends ConcreteStar { // 注意:这里用了继承
@Override public void perform() {
System.out.println("经纪人:安排场地、对接流程...");
super.perform(); // 调用父类(明星)的方法
System.out.println("经纪人:处理费用结算、安排下一场...");
}
}
// 测试
public class InheritanceProxyTest {
public static void main(String[] args) {
Star agent = new StarAgentProxy(); // 代理对象是真实明星的子类
agent.perform();
}
}
1. 强耦合:代理类绑定具体实现,而非抽象
-
问题 :
StarAgentProxy
直接继承ConcreteStar
(具体类),而非Star
(接口)。
如果明星类需要更换实现(比如新增一个AdvancedStar
类),代理类必须跟着修改继承关系,违背了 "依赖抽象而非具体" 的设计原则。// 假设新增一个更高级的明星类 class AdvancedStar implements Star { /* ... */ } // 此时代理类若想代理AdvancedStar,必须修改继承关系,违反开闭原则 class StarAgentProxy extends AdvancedStar { /* ... */ }
2. 单继承限制:无法代理多个对象或扩展其他功能
- Java 特性:单继承机制导致代理类只能继承一个具体类,无法同时代理多个不同的目标对象(如同时代理 "歌手" 和 "演员" 两个不同类)。
- 扩展性差:如果代理类需要额外功能(如日志记录),只能通过多层继承实现,导致类层次结构复杂,违背 "组合优于继承" 原则。
3. 违背接口隔离:代理类依赖目标类的具体实现
- 本质问题 :代理模式的核心是 "控制对目标对象的访问",而继承方式让代理类直接拥有目标类的所有方法和属性(包括不需要的)。
例如:如果ConcreteStar
新增一个sing()
方法,代理类会自动继承该方法,而代理类可能并不需要这个方法,导致接口污染。
七、代理模式的正确打开方式:组合(Has-A)而非继承(Is-A)
核心思想
代理类与目标对象实现相同接口 (基于抽象),通过构造方法持有目标对象的引用(组合关系),而非继承具体类。这样:
- 代理类只依赖接口(低耦合),可代理任何实现该接口的目标对象
- 遵循 "开闭原则",新增目标类时无需修改代理类
- 灵活扩展,可在运行时动态替换目标对象
八、两种实现方式对比表
特性 | 继承式代理 | 组合式代理(正确方式) |
---|---|---|
耦合度 | 依赖具体类(强耦合) | 依赖接口(低耦合) |
开闭原则 | 新增目标类需修改代理类(违反) | 可直接代理新目标类(符合) |
扩展性 | 单继承限制,功能扩展困难 | 可自由组合其他功能(如日志) |
适用场景 | 仅能代理特定类 | 可代理所有实现接口的类 |
设计原则 | 违背 "依赖抽象" 原则 | 严格遵循接口隔离原则 |
九、静态代理的优缺点
优点:
- 符合开闭原则:不修改真实对象代码,通过代理类扩展功能。
- 职责清晰:代理与真实对象分工明确,代码易维护。
- 保护真实对象:代理可控制对真实对象的访问(如权限校验、日志记录)。
缺点:
- 类爆炸问题:每个真实对象需对应一个代理类,若接口方法多或真实对象多,会导致代理类数量膨胀。
- 灵活性有限:代理类的逻辑固定,若需通用增强逻辑(如所有方法都加日志),静态代理难以复用。
大家思考一下:如果系统中业务接口很多,一个接口对应一个代理类,显然也是不合理的,会导致类爆炸。怎么解决这个问题?
**动态代理可以解决。**因为在动态代理中可以在内存中动态的为我们生成代理类的字节码。代理类不需要我们写了。类爆炸解决了,而且代码只需要写一次,代码也会得到复用。
十、动态代理
1.动态代理概述
静态代理虽然能在不修改目标对象的基础上增强其功能,但存在类爆炸的问题。即每有一个目标对象,就需要为其创建一个对应的代理类。而动态代理则很好地解决了这个问题,它可以在运行时动态地生成代理类,无需为每个目标对象都手动编写代理类。
在 Java 中,实现动态代理主要有两种方式:基于 java.lang.reflect.Proxy
和 InvocationHandler
的 JDK 动态代理 ,以及基于 CGLIB 的动态代理。下面将分别详细介绍这两种方式。
2.JDK 动态代理
⑴.原理
JDK 动态代理是基于接口的代理方式。它通过 java.lang.reflect.Proxy
类和 java.lang.reflect.InvocationHandler
接口来实现。Proxy
类负责生成代理类的实例,而 InvocationHandler
接口则用于定义代理对象方法调用时的处理逻辑。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 定义一个公共接口,代表明星的表演行为
interface Star {
// 表演方法
void perform();
}
// 具体的明星类,实现 Star 接口
class ConcreteStar implements Star {
@Override
public void perform() {
System.out.println("明星开始精彩的表演!");
}
}
// 动态代理的调用处理器
class StarInvocationHandler implements InvocationHandler {
// 持有目标对象的引用
private final Object target;
// 构造方法,接收目标对象作为参数
public StarInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在调用目标对象的方法之前可以添加额外操作
System.out.println("经纪人安排场地、宣传等准备工作。");
// 调用目标对象的方法
Object result = method.invoke(target, args);
// 在调用目标对象的方法之后可以添加额外操作
System.out.println("经纪人处理后续事务,如结算费用等。");
return result;
}
}
// 测试类
public class JdkDynamicProxyTest {
public static void main(String[] args) {
// 创建具体的明星对象
Star realStar = new ConcreteStar();
// 创建动态代理的调用处理器,并传入目标对象
StarInvocationHandler handler = new StarInvocationHandler(realStar);
// 使用 Proxy 类的 newProxyInstance 方法创建动态代理对象
Star proxy = (Star) Proxy.newProxyInstance(
Star.class.getClassLoader(),
new Class<?>[]{Star.class},
handler
);
// 调用动态代理对象的方法
proxy.perform();
}
}
代码解释
Star
接口 :定义了明星的核心行为perform()
,这是代理对象和目标对象都要实现的公共接口。ConcreteStar
类 :具体的明星类,也就是目标对象,实现了perform()
方法。StarInvocationHandler
类 :实现了InvocationHandler
接口,其中的invoke
方法是核心,它**会在代理对象的方法被调用时自动执行。**在invoke
方法中,可以在调用目标对象的方法前后添加额外的操作。

⑵.方法解释
Proxy.newProxyInstance
方法定义在 java.lang.reflect.Proxy
类中,其签名如下:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
参数解释
ClassLoader loader
- 作用:指定用于加载代理类的类加载器。类加载器负责将代理类的字节码加载到 Java 虚拟机(JVM)中。通常使用目标对象所实现接口的类加载器,因为代理类需要与目标对象实现相同的接口,使用相同的类加载器可以确保代理类和接口在同一个类加载环境中。
- 代码示例中的情况 :
Star.class.getClassLoader()
获取了Star
接口的类加载器。由于Star
接口是目标对象ConcreteStar
所实现的接口,使用这个类加载器可以保证代理类能够正确加载并实现Star
接口。
Class<?>[] interfaces
- 作用:指定代理类要实现的接口数组。代理类会实现这些接口中定义的所有方法,这样代理对象就可以像目标对象一样被使用,因为它们实现了相同的接口。
- 代码示例中的情况 :
new Class<?>[]{Star.class}
表示代理类要实现Star
接口。这里使用数组是因为代理类可以同时实现多个接口,例如new Class<?>[]{Interface1.class, Interface2.class}
表示代理类将同时实现Interface1
和Interface2
两个接口。
InvocationHandler h
- 作用 :指定代理对象方法调用的处理程序。当调用代理对象的方法时,实际上会调用
InvocationHandler
中的invoke
方法。在invoke
方法中,可以添加额外的逻辑,如在调用目标对象的方法前后进行一些操作,然后再调用目标对象的实际方法。- 代码示例中的情况 :
handler
是StarInvocationHandler
类的实例。StarInvocationHandler
实现了InvocationHandler
接口,并重写了invoke
方法,在该方法中添加了经纪人在明星表演前后的操作逻辑。返回值
- 作用 :返回一个实现了指定接口的代理对象。这个代理对象可以像目标对象一样调用接口中定义的方法,但在调用方法时会经过
InvocationHandler
的处理。- 代码示例中的情况 :
(Star) Proxy.newProxyInstance(...)
将返回的代理对象强制转换为Star
类型,因为代理类实现了Star
接口。这样就可以通过proxy
变量调用Star
接口中定义的perform
方法。
⑶.通过 Star 类型的引用变量调用 perform 方法时,为什么是调用了代理对象实现的 perform 方法。
在Java的动态代理机制中,当通过Star类型的引用变量调用perform()方法时,实际上调用的是代理对象的方法,而代理对象内部通过InvocationHandler将调用转发给真实对象,并允许在调用前后插入额外逻辑。以下是详细的步骤解析:
① 动态代理的核心机制
动态代理对象是通过Proxy.newProxyInstance动态生成的类实例,它实现了指定的接口(如Star)。
代理对象的所有方法调用都会被重定向到InvocationHandler的invoke方法。
②代理对象的创建过程
在JdkDynamicProxyTest类中:
// 创建代理对象
Star proxy = (Star) Proxy.newProxyInstance(
Star.class.getClassLoader(),
new Class<?>[]{Star.class},
handler
);
-
类加载器 :
Star.class.getClassLoader()
用于加载代理类。 -
接口列表 :
new Class<?>[]{Star.class}
表明代理类会实现Star
接口。 -
调用处理器 :
handler
是StarInvocationHandler
实例,负责处理实际的方法调用。
③ 代理对象的方法调用流程
当调用proxy.perform()时:
代理对象拦截调用
动态生成的代理类覆盖了Star接口的所有方法(如perform())。当调用perform()时,代理类的方法实现会触发InvocationHandler.invoke()。
转发到InvocationHandler的invoke方法
代理类的方法实现类似以下伪代码:
public void perform() {
handler.invoke(
this, // 代理对象自身
methodPerform, // 通过反射获取的Method对象(对应perform()方法)
new Object[0] // 方法参数(本例中无参数)
);
}
在invoke
方法中处理逻辑
StarInvocationHandler
的invoke
方法执行以下操作:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置处理(如经纪人准备工作)
System.out.println("经纪人安排场地、宣传等准备工作。");
// 调用真实对象的方法(通过反射)
Object result = method.invoke(target, args);
// 后置处理(如结算费用)
System.out.println("经纪人处理后续事务,如结算费用等。");
return result;
}
method.invoke(target, args)会调用真实对象(ConcreteStar实例)的perform()方法。 通过这种方式,代理对象在调用真实对象方法前后添加了额外逻辑。
④关键:代理对象与接口的关系
代理对象是接口的实现类
代理对象动态实现了Star接口,因此可以赋值给Star类型的变量。
方法调用的本质是动态分发
当通过Star接口调用perform()时,实际调用的是代理对象的实现方法,而代理对象的实现方法会委托给InvocationHandler。
⑤ 为什么不是直接调用真实对象的方法?
代理对象的目的是增强行为
动态代理的核心思想是"控制访问"------通过代理对象间接调用真实对象,从而在调用前后插入逻辑(如日志、事务管理等)。
透明性
调用者(如JdkDynamicProxyTest类)无需知道实际调用的是代理对象还是真实对象,只需面向接口编程。
总结
当调用proxy.perform()时,代理对象(动态生成的类)拦截了方法调用,并通过InvocationHandler将调用转发给真实对象。
InvocationHandler的invoke方法负责协调调用过程,既调用了真实对象的方法,又添加了额外逻辑。
动态代理通过接口实现透明性,使得调用者无需关心底层是直接调用还是代理增强。
⑷.invoke
方法
invoke
方法是 java.lang.reflect.InvocationHandler
接口中定义的唯一方法,当调用动态代理对象的方法时,实际上会执行 InvocationHandler
实现类中的 invoke
方法。下面详细解释 invoke
方法的各个参数以及方法体的逻辑。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
参数解释
Object proxy
- 作用 :表示生成的动态代理对象本身。这个对象是通过
Proxy.newProxyInstance
方法创建出来的代理对象。不过在invoke
方法中,通常不会直接使用proxy
对象,因为如果在invoke
方法中再次调用proxy
对象的方法,会导致无限递归调用,因为每次调用代理对象的方法都会触发invoke
方法。- 示例代码中的情况 :在当前的
StarInvocationHandler
类的invoke
方法中,并没有使用proxy
参数。
Method method
- 作用 :表示被调用的代理对象的方法。
Method
类是 Java 反射机制中的一个重要类,它包含了方法的各种信息,如方法名、参数类型、返回值类型等。通过method
对象,可以使用反射机制调用目标对象的对应方法。- 示例代码中的情况 :在
invoke
方法中,method.invoke(target, args)
这行代码使用method
对象调用了目标对象target
的对应方法。
Object[] args
- 作用 :表示调用代理对象方法时传递的参数数组。如果调用的方法没有参数,
args
数组将为null
;如果有参数,args
数组中的元素将按照方法参数的顺序依次排列。- 示例代码中的情况 :在
Star
接口的perform
方法没有参数,所以在调用proxy.perform()
时,args
数组为null
。但如果Star
接口的方法有参数,这些参数会被封装在args
数组中传递给invoke
方法。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在调用目标对象的方法之前可以添加额外操作
System.out.println("经纪人安排场地、宣传等准备工作。");
// 调用目标对象的方法
Object result = method.invoke(target, args);
// 在调用目标对象的方法之后可以添加额外操作
System.out.println("经纪人处理后续事务,如结算费用等。");
return result;
}
调用目标对象的方法
Object result = method.invoke(target, args);
使用 Method
对象的 invoke
方法调用目标对象 target
的对应方法,并将调用结果存储在 result
变量中。invoke
方法的第一个参数是要调用方法的对象,这里是目标对象 target
;第二个参数是调用方法时传递的参数数组,即 args
。
返回结果
return result;
将目标对象方法的调用结果返回,这样调用代理对象方法的代码就可以获取到目标对象方法的返回值。
CGLIB 动态代理:无接口场景下的代理方案
一、为什么需要 CGLIB 动态代理?
前文介绍的 JDK 动态代理基于接口实现,要求目标对象必须实现至少一个接口。但现实中存在大量没有接口的类 (如遗留代码、第三方工具类),此时 JDK 代理失效。
CGLIB 动态代理 应运而生:它通过生成目标类的子类实现代理,无需目标类实现接口,弥补了 JDK 代理的局限性。
二、CGLIB 核心原理
1. 核心思想
- 继承代理:CGLIB 在运行时动态生成目标类的子类(代理类),代理类重写目标类的方法。
- 方法拦截 :通过回调函数(
MethodInterceptor
)拦截方法调用,在子类方法中添加增强逻辑(如前置 / 后置处理)。
2. 核心类
Enhancer
:CGLIB 的核心类,用于设置代理类的父类(目标类)和回调函数,生成代理对象。MethodInterceptor
:核心接口,定义方法拦截逻辑,相当于 JDK 代理中的InvocationHandler
。MethodProxy
:CGLIB 的方法代理类,用于高效调用目标类的方法(比反射更快)。
三、代码实战:用 CGLIB 代理 "无接口明星"
场景说明
假设明星类ConcreteStar
没有实现任何接口(现实中可能存在的遗留类),需要经纪人代理其表演流程。
1. 添加 CGLIB 依赖
Maven 项目需在pom.xml
中添加:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
2. 目标类(无接口)
// 没有实现任何接口的明星类
class ConcreteStar {
public void perform() { // 核心业务方法
System.out.println("明星开始精彩的表演!");
}
// 假设这是一个不需要代理的final方法(CGLIB无法代理final方法)
public final void sing() {
System.out.println("明星演唱一首歌曲");
}
}
3. CGLIB 代理类(方法拦截器)
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
// 实现MethodInterceptor接口定义拦截逻辑
class StarCglibProxy implements MethodInterceptor {
// 目标对象(被代理的真实对象)
private final Object target;
public StarCglibProxy(Object target) {
this.target = target;
}
@Override
public Object intercept(
Object proxy, // 代理对象(子类实例)
Method method, // 被调用的目标方法
Object[] args, // 方法参数
MethodProxy proxyMethod // CGLIB的方法代理,用于调用目标方法
) throws Throwable {
// 前置处理:经纪人准备工作
System.out.println("经纪人:安排场地、调试设备、对接流程...");
// 方式1:通过反射调用目标方法(与JDK代理类似)
// Object result = method.invoke(target, args);
// 方式2:通过CGLIB的MethodProxy调用(更高效)
Object result = proxyMethod.invokeSuper(proxy, args); // 调用父类(目标类)的方法
// 后置处理:经纪人收尾工作
System.out.println("经纪人:结算费用、安排下一场行程...");
return result; // 返回目标方法的执行结果
}
}
4. 客户端测试
import net.sf.cglib.proxy.Enhancer;
public class CglibProxyTest {
public static void main(String[] args) {
// 1. 创建目标对象
ConcreteStar realStar = new ConcreteStar();
// 2. 创建CGLIB增强器
Enhancer enhancer = new Enhancer();
// 3. 设置代理类的父类(即目标类)
enhancer.setSuperclass(realStar.getClass());
// 4. 设置回调函数(方法拦截器)
enhancer.setCallback(new StarCglibProxy(realStar));
// 5. 生成代理对象(目标类的子类)
ConcreteStar proxyStar = (ConcreteStar) enhancer.create();
// 6. 调用代理对象的方法
proxyStar.perform(); // 正常代理
// 测试final方法:CGLIB无法代理,会直接调用目标类的方法
proxyStar.sing(); // 输出:明星演唱一首歌曲(无代理逻辑)
}
}
对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数:
填写 VM 选项
-
在左侧列表中选择你的运行配置(如
Client
)。 -
在右侧的 VM options 输入框中,将两个参数合并为一行输入:
--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/sun.net.util=ALL-UNNAMED
四、CGLIB 关键细节解析
1. intercept
方法参数对比
参数名 | 含义 | 与 JDK 代理的区别 |
---|---|---|
proxy |
代理对象(目标类的子类实例) | JDK 代理中是接口类型,CGLIB 中是子类实例 |
method |
被调用的目标方法(反射对象) | 与 JDK 代理的Method 参数含义相同 |
args |
方法参数数组 | 与 JDK 代理相同 |
proxyMethod |
CGLIB 的方法代理对象,用于高效调用目标方法 | JDK 代理中无此参数,需通过method.invoke |
2. 调用目标方法的两种方式
- 反射调用(
method.invoke
):与 JDK 代理一致,兼容性强但性能稍低。 - CGLIB 专用调用(
proxyMethod.invokeSuper
) :- 内部通过字节码生成技术直接调用父类方法,性能比反射快5-10 倍。
- 注意:
proxyMethod.invoke(proxy, args)
会递归调用代理类的方法(导致死循环),需用invokeSuper
调用父类(目标类)方法。
3. 代理范围限制
- 无法代理
final
类 :CGLIB 通过继承实现代理,final
类不能被继承。 - 无法代理
final
方法 :final
方法不能被重写,会直接调用目标类的方法(如示例中的sing()
方法)。 - 构造方法无法代理:代理类继承目标类,构造方法由目标类初始化。
五、CGLIB vs JDK 动态代理:核心对比
特性 | JDK 动态代理 | CGLIB 动态代理 |
---|---|---|
代理基础 | 接口(必须实现至少一个接口) | 类(生成目标类的子类) |
性能 | 反射调用,稍慢 | 字节码生成,调用效率更高 |
适用场景 | 有接口的目标对象 | 无接口、或需要代理类的场景 |
局限性 | 依赖接口,无法代理无接口类 | 无法代理 final 类 / 方法,继承可能破坏封装 |
Spring 中的应用 | 代理接口(如@Transactional 接口方法) |
代理类(如无接口的组件,或@Configuration 类) |
六、CGLIB 适用场景
- 无接口类代理 :处理遗留代码、第三方工具类(如
HashMap
、ArrayList
等没有接口的类)。 - 高性能需求 :
MethodProxy
比反射调用更快,适合对性能敏感的场景(如高频调用的工具类)。 - AOP 底层实现 :Spring 框架中,当 Bean 没有实现接口时,默认使用 CGLIB 代理(可通过
proxy-target-class
配置强制使用)。 - 字节码增强:除了方法拦截,CGLIB 还可用于动态生成类、修改类结构(如添加字段、方法)。
七、CGLIB 代理的优缺点
优点:
- 无接口依赖:弥补 JDK 代理的局限性,适用范围更广。
- 高性能 :通过字节码生成和
MethodProxy
优化调用效率。 - 灵活扩展:可代理类的所有非 final 方法,支持更细粒度的拦截。
缺点:
- 继承限制:代理类是目标类的子类,可能破坏类的封装性(如覆盖父类方法)。
- final 方法限制 :无法代理
final
修饰的类或方法。 - 类加载问题:生成的代理类可能导致类加载器相关问题(如多模块环境)。
八、最佳实践
- 优先接口代理:如果目标类有接口,优先使用 JDK 动态代理(更符合面向接口设计)。
- 合理处理 final 成员 :避免在目标类中对关键方法使用
final
修饰,确保 CGLIB 可代理。 - 结合 Spring 配置 :在 Spring 中通过
@EnableAspectJAutoProxy(proxyTargetClass = true)
强制使用 CGLIB 代理类。 - 性能敏感场景 :对高频调用的方法,优先使用 CGLIB 的
MethodProxy.invokeSuper
而非反射调用。
九、总结:CGLIB 的核心价值
CGLIB 动态代理是 JDK 代理的重要补充,解决了无接口类的代理问题,在框架设计(如 Spring AOP)和遗留代码处理中不可或缺。其核心思想是通过继承 + 方法拦截实现代理,相比 JDK 代理更灵活但也有更多限制。
理解 CGLIB 与 JDK 代理的区别,能帮助我们在不同场景下选择合适的代理方案:
- 有接口 → JDK 动态代理(优雅、符合设计原则)
- 无接口 → CGLIB 代理(强制代理,牺牲部分封装性)
通过动态代理技术,我们实现了 "在不修改目标对象的前提下扩展功能",这正是 AOP(面向切面编程)的核心思想。掌握 CGLIB,能让我们在面对复杂代码结构时更游刃有余,写出更具扩展性的代码。