本文分享自华为云社区《
Spring高手之路17——动态代理的艺术与实践
》,作者: 砖业洋__。
1. 背景
动态代理是一种强大的设计模式,它允许开发者在运行时创建代理对象,用于拦截对真实对象的方法调用。这种技术在实现面向切面编程(
AOP
)、事务管理、权限控制等功能时特别有用,因为它可以在不修改原有代码结构的前提下,为程序动态地注入额外的逻辑。
2. JDK动态代理
2.1 定义和演示
JDK
动态代理是
Java
语言提供的一种基于接口的代理机制,允许开发者在运行时动态地创建代理对象,而无需为每个类编写具体的代理实现。
这种机制主要通过
java.lang.reflect.Proxy
类和
java.lang.reflect.InvocationHandler
接口实现。下面是
JDK
动态代理的核心要点和如何使用它们的概述。
使用步骤
定义接口:首先定义一个或多个接口,代理对象将实现这些接口。
实现接口:创建一个类,它实现上述接口,提供具体的实现逻辑。
创建
InvocationHandler
实现:定义一个
InvocationHandler
的实现,这个实现中的
invoke
方法可以包含自定义逻辑。
创建代理对象:使用
Proxy.newProxyInstance
方法,传入目标对象的类加载器、需要代理的接口数组以及
InvocationHandler
的实现,来创建一个实现了指定接口的代理对象。
用简单的例子来说明这个过程,全部代码如下:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;interfaceHelloWorld {voidsayHello();
}classHelloWorldImpl implements HelloWorld {public voidsayHello() {
System.out.println("Hello world!");
}
}public classDemoApplication {public static voidmain(String[] args) {
HelloWorldImpl realObject= newHelloWorldImpl();
HelloWorld proxyInstance=(HelloWorld) Proxy.newProxyInstance(
HelloWorldImpl.class.getClassLoader(), //使用目标类的类加载器
new Class[]{HelloWorld.class}, //代理类需要实现的接口列表
newInvocationHandler() {
@OverridepublicObject invoke(Object proxy, Method method, Object[] args) throws Throwable {//在调用目标方法前可以插入自定义逻辑
System.out.println("Before method call");//调用目标对象的方法
Object result =method.invoke(realObject, args);//在调用目标方法后可以插入自定义逻辑
System.out.println("After method call");returnresult;
}
});
proxyInstance.sayHello();
}
}
运行结果如下:

InvocationHandler
是动态代理的核心接口之一,当我们使用动态代理模式创建代理对象时,任何对代理对象的方法调用都会被转发到一个实现了
InvocationHandler
接口的实例的
invoke
方法上。
我们经常看到
InvocationHandler
动态代理的匿名内部类,这会在代理对象的相应方法被调用时执行。具体地说,每当对代理对象执行方法调用时,调用的方法不会直接执行,而是转发到实现了
InvocationHandler
的
invoke
方法上。在这个
invoke
方法内部,我们可以定义拦截逻辑、调用原始对象的方法、修改返回值等操作。
在这个例子中,当调用
proxyInstance.sayHello()
方法时,实际上执行的是
InvocationHandler
的匿名内部类中的
invoke
方法。这个方法中,我们可以在调用实际对象的
sayHello
方法前后添加自定义逻辑(比如这里的打印消息)。这就是动态代理和
InvocationHandler
的工作原理。
我们来看关键的一句代码
Object result = method.invoke(realObject, args);
在
Java
的动态代理中,
method.invoke(realObject, args)
这句代码扮演着核心的角色,因为它实现了代理对象方法调用的转发机制。下面分别解释一下这行代码的两个主要部分:
method.invoke()
方法和
args
参数。
method.invoke(realObject, args)
作用:这行代码的作用是调用目标对象(
realObject
)的具体方法。在动态代理的上下文中,
invoke
方法是在代理实例上调用方法时被自动调用的。通过这种方式可以在实际的方法执行前后加入自定义的逻辑,比如日志记录、权限检查等。
method:
method
是一个
java.lang.reflect.Method
类的实例,代表了正在被调用的方法。在
invoke
方法中,这个对象用来标识代理对象上被调用的具体方法。
注意:如果尝试直接在
invoke
方法内部使用
method.invoke(proxy, args)
调用代理对象的方法,而不是调用原始目标对象的方法,则会导致无限循环。这是因为调用
proxy
实例上的方法会再次被代理拦截,从而无限调用
invoke
方法,传参可别传错了。
- invoke:
Method
类的
invoke
方法用于执行指定方法。第一个参数是指明方法应该在哪个对象上调用(在这个例子中是
realObject
),后续的参数
args
是调用方法时传递的参数。
args
2.2 不同方法分别代理
我们继续通过扩展
HelloWorld
接口来包含多个方法,并通过
JDK
动态代理演示权限控制和功能开关操作的一种实现方式
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;interfaceHelloWorld {voidsayHello();voidsayGoodbye();
}classHelloWorldImpl implements HelloWorld {public voidsayHello() {
System.out.println("Hello world!");
}public voidsayGoodbye() {
System.out.println("Goodbye world!");
}
}public classDemoApplication {public static voidmain(String[] args) {
HelloWorld realObject= newHelloWorldImpl();
HelloWorld proxyInstance=(HelloWorld) Proxy.newProxyInstance(
HelloWorldImpl.class.getClassLoader(),new Class[]{HelloWorld.class},newInvocationHandler() {//添加一个简单的权限控制演示
private boolean accessAllowed = true;//简单的功能开关
private boolean goodbyeFunctionEnabled = true;
@OverridepublicObject invoke(Object proxy, Method method, Object[] args) throws Throwable {//权限控制
if (!accessAllowed) {
System.out.println("Access denied");return null; //在实际场景中,可以抛出一个异常
}//功能开关
if (method.getName().equals("sayGoodbye") && !goodbyeFunctionEnabled) {
System.out.println("Goodbye function is disabled");return null;
}//方法执行前的通用逻辑
System.out.println("Before method:" +method.getName());//执行方法
Object result =method.invoke(realObject, args);//方法执行后的通用逻辑
System.out.println("After method:" +method.getName());returnresult;
}
});//正常执行
proxyInstance.sayHello();//可以根据goodbyeFunctionEnabled变量决定是否执行
proxyInstance.sayGoodbye();
}
}
运行如下:

如果
accessAllowed
变量为
false
:

如果
goodbyeFunctionEnabled
变量为
false
:

在这个例子中:
这个例子展示了
JDK
动态代理在实际应用中如何进行方法级别的细粒度控制,同时保持代码的灵活性和可维护性。通过动态代理,我们可以在不修改原始类代码的情况下,为对象动态地添加额外的行为。
2.3 熔断限流和日志监控
为了更全面地展示
JDK
动态代理的能力,我们在先前的示例中添加熔断限流和日志监控的逻辑。这些是在高并发和分布式系统中常见的需求,可以通过动态代理以非侵入式的方式实现。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;interfaceHelloWorld {voidsayHello();
}classHelloWorldImpl implements HelloWorld {public voidsayHello() {
System.out.println("Hello world!");
}
}public classDemoApplication {public static voidmain(String[] args) {
HelloWorld realObject= newHelloWorldImpl();
HelloWorld proxyInstance=(HelloWorld) Proxy.newProxyInstance(
HelloWorldImpl.class.getClassLoader(),new Class[]{HelloWorld.class},newAdvancedInvocationHandler(realObject));//模拟多次调用以观察限流和熔断效果
for (int i = 0; i < 10; i++) {
proxyInstance.sayHello();
}
}static classAdvancedInvocationHandler implements InvocationHandler {privatefinal Object target;private AtomicInteger requestCount = new AtomicInteger(0);private AtomicLong lastTimestamp = newAtomicLong(System.currentTimeMillis());private volatile boolean circuitBreakerOpen = false;private final long cooldownPeriod = 10000; //冷却时间10秒
publicAdvancedInvocationHandler(Object target) {this.target =target;
}
@OverridepublicObject invoke(Object proxy, Method method, Object[] args) throws Throwable {long now =System.currentTimeMillis();//检查熔断器是否应该被重置
if (circuitBreakerOpen && (now - lastTimestamp.get() >cooldownPeriod)) {
circuitBreakerOpen= false; //重置熔断器
requestCount.set(0); //重置请求计数
System.out.println("Circuit breaker has been reset.");
}//熔断检查
if(circuitBreakerOpen) {
System.out.println("Circuit breaker is open. Blocking method execution for:" +method.getName());return null; //在实际场景中,可以返回一个兜底的响应或抛出异常
}//限流检查
if (requestCount.incrementAndGet() > 5) {if (now - lastTimestamp.get() < cooldownPeriod) { //10秒内超过5次请求,触发熔断
circuitBreakerOpen = true;
lastTimestamp.set(now); //更新时间戳
System.out.println("Too many requests. Opening circuit breaker.");return null; //触发熔断时的处理
} else{//重置计数器和时间戳
requestCount.set(0);
lastTimestamp.set(now);
}
}//执行实际方法
Object result =method.invoke(target, args);//方法执行后的逻辑
System.out.println("Executed method:" +method.getName());returnresult;
}
}
}

在这个扩展示例中,我们实现了:
熔断机制:通过一个简单的计数器和时间戳来模拟。如果在
10
秒内对任一方法的调用次数超过
5
次,我们就"打开"熔断器,阻止进一步的方法调用。在实际应用中,熔断逻辑可能更加复杂,可能包括错误率的检查、调用延迟的监控等。
限流:这里使用的限流策略很简单,通过计数和时间戳来判断是否在短时间内请求过多。在更复杂的场景中,可以使用令牌桶或漏桶算法等更高级的限流策略。
日志监控:在方法调用前后打印日志,这对于监控系统的行为和性能是非常有用的。在实际项目中,这些日志可以集成到日志管理系统中,用于问题诊断和性能分析。
通过在
invoke
方法中加入这些逻辑,我们能够在不修改原有业务代码的情况下,为系统添加复杂的控制和监控功能。如果到达流量阈值或系统处于熔断状态,可以阻止对后端服务的进一步调用,直接返回一个默认值或错误响应,避免系统过载。
3. CGLIB动态代理
CGLIB
(
Code Generation Library
)是一个强大的高性能代码生成库,它在运行时动态生成新的类。与
JDK
动态代理不同,
CGLIB
能够代理那些没有实现接口的类。这使得
CGLIB
成为那些因为设计限制或其他原因不能使用接口的场景的理想选择。
3.1 定义和演示
工作原理
CGLIB
通过继承目标类并在运行时生成子类来实现动态代理。代理类覆盖了目标类的非
final
方法,并在调用方法前后提供了注入自定义逻辑的能力。这种方法的一个关键优势是它不需要目标对象实现任何接口。
使用CGLIB的步骤
添加CGLIB依赖:首先,需要在项目中添加
CGLIB
库的依赖。
如果使用
Maven
,可以添加如下依赖到
pom.xml
中:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version> <!-- 目前最新的版本 -->
</dependency>
创建MethodInterceptor:实现
MethodInterceptor
接口,这是
CGLIB
提供的回调类型,用于定义方法调用的拦截逻辑。
生成代理对象:使用
Enhancer
类来创建代理对象。
Enhancer
是
CGLIB
中用于生成新类的类。
改造一下
1.1
节的例子,可以对比看看,全部示例代码如下:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;classHelloWorld {public voidsayHello() {
System.out.println("Hello world!");
}
}public classDemoApplication {public static voidmain(String[] args) {
Enhancer enhancer= newEnhancer();//设置需要代理的类
enhancer.setSuperclass(HelloWorld.class);
enhancer.setCallback(newMethodInterceptor() {
@OverridepublicObject intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method call");
Object result= proxy.invokeSuper(obj, args); //调用父类的方法
System.out.println("After method call");returnresult;
}
});
HelloWorld proxy= (HelloWorld) enhancer.create(); //创建代理对象
proxy.sayHello(); //通过代理对象调用方法
}
}
运行结果如下:

CGLIB vs JDK动态代理
- 接口要求:
JDK
动态代理只能代理实现了接口的对象,而
CGLIB
能够直接代理类。
- 性能:
CGLIB
在生成代理对象时通常比
JDK
动态代理要慢,因为它需要动态生成新的类。但在调用代理方法时,
CGLIB
通常会提供更好的性能。
- 方法限制:
CGLIB
不能代理
final
方法,因为它们不能被子类覆盖。
CGLIB
是一个强大的工具,特别适用于需要代理没有实现接口的类的场景。然而,选择
JDK
动态代理还是
CGLIB
主要取决于具体的应用场景和性能要求。
注意:在
CGLIB
中,如果使用
MethodProxy.invoke(obj, args)
,而不是
MethodProxy.invokeSuper(obj, args)
,并且
obj
是代理实例本身(
CGLIB
通过
Enhancer
创建的代理对象,而不是原始的被代理的目标对象),就会导致无限循环。
invoke
方法实际上是尝试在传递的对象上调用方法,如果该对象是代理对象,则调用会再次被拦截,造成无限循环。
3.2 不同方法分别代理(对比JDK动态代理写法)
我们改写
1.2
节的例子
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;classHelloWorldImpl {public voidsayHello() {
System.out.println("Hello world!");
}public voidsayGoodbye() {
System.out.println("Goodbye world!");
}
}public classDemoApplication {public static voidmain(String[] args) {
Enhancer enhancer= newEnhancer();
enhancer.setSuperclass(HelloWorldImpl.class); //设置被代理的类
enhancer.setCallback(newMethodInterceptor() {//添加一个简单的权限控制演示
private boolean accessAllowed = true;//简单的功能开关
private boolean goodbyeFunctionEnabled = true;
@OverridepublicObject intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {//权限控制
if (!accessAllowed) {
System.out.println("Access denied");return null; //在实际场景中,可以抛出一个异常
}//功能开关
if (method.getName().equals("sayGoodbye") && !goodbyeFunctionEnabled) {
System.out.println("Goodbye function is disabled");return null;
}//方法执行前的通用逻辑
System.out.println("Before method:" +method.getName());//执行方法
Object result =proxy.invokeSuper(obj, args);//方法执行后的通用逻辑
System.out.println("After method:" +method.getName());returnresult;
}
});
HelloWorldImpl proxyInstance= (HelloWorldImpl) enhancer.create(); //创建代理对象
proxyInstance.sayHello(); //正常执行
proxyInstance.sayGoodbye(); //可以根据goodbyeFunctionEnabled变量决定是否执行
}
}
运行结果如下:

我们需要注意几点更改:
因为
CGLIB
不是基于接口的代理,而是通过生成目标类的子类来实现代理,所以我们不再需要接口
HelloWorld
。
我们将使用
Enhancer
类来创建代理实例,并提供一个
MethodInterceptor
来处理方法调用。
3.3 熔断限流和日志监控(对比JDK动态代理写法)
我们改写
1.3
节的例子
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;classHelloWorld {voidsayHello() {
System.out.println("Hello world!");
}
}public classDemoApplication {public static voidmain(String[] args) {
HelloWorld realObject= newHelloWorld();
HelloWorld proxyInstance=(HelloWorld) createProxy(realObject);//模拟多次调用以观察限流和熔断效果
for (int i = 0; i < 10; i++) {
proxyInstance.sayHello();
}
}public staticObject createProxy(final Object realObject) {
Enhancer enhancer= newEnhancer();
enhancer.setSuperclass(HelloWorld.class);
enhancer.setCallback(newAdvancedMethodInterceptor(realObject));returnenhancer.create();
}static classAdvancedMethodInterceptor implements MethodInterceptor {privatefinal Object target;private final AtomicInteger requestCount = new AtomicInteger(0);private final AtomicLong lastTimestamp = newAtomicLong(System.currentTimeMillis());private volatile boolean circuitBreakerOpen = false;private final long cooldownPeriod = 10000; //冷却时间10秒
publicAdvancedMethodInterceptor(Object target) {this.target =target;
}
@OverridepublicObject intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {long now =System.currentTimeMillis();//检查熔断器是否应该被重置
if (circuitBreakerOpen && (now - lastTimestamp.get() >cooldownPeriod)) {
circuitBreakerOpen= false; //重置熔断器
requestCount.set(0); //重置请求计数
System.out.println("Circuit breaker has been reset.");
}//熔断检查
if(circuitBreakerOpen) {
System.out.println("Circuit breaker is open. Blocking method execution for:" +method.getName());return null; //在实际场景中,可以返回一个兜底的响应或抛出异常
}//限流检查
if (requestCount.incrementAndGet() > 5) {if (now - lastTimestamp.get() < cooldownPeriod) { //10秒内超过5次请求,触发熔断
circuitBreakerOpen = true;
lastTimestamp.set(now); //更新时间戳
System.out.println("Too many requests. Opening circuit breaker.");return null; //触发熔断时的处理
} else{//重置计数器和时间戳
requestCount.set(0);
lastTimestamp.set(now);
}
}//执行实际方法
Object result = proxy.invokeSuper(obj, args); //注意这里调用的是invokeSuper//方法执行后的逻辑
System.out.println("Executed method:" +method.getName());returnresult;
}
}
}
运行结果
在这个改写中,我们使用
CGLIB
的
Enhancer
和
MethodInterceptor
来代替了
JDK
的
Proxy
和
InvocationHandler
。
MethodInterceptor
的
intercept
方法与
InvocationHandler
的
invoke
方法在概念上是相似的,但它使用
MethodProxy
的
invokeSuper
方法来调用原始类的方法,而不是使用反射。这允许
CGLIB
在运行时生成代理类的字节码,而不是依赖于反射,从而提高了性能。此外,
circuitBreakerOpen
被声明为
volatile
,是确保其在多线程环境中的可见性。
4. 动态代理图示
方法调用拦截:
客户端通过代理对象调用方法,此时方法调用被代理对象拦截。
转发给处理器或方法拦截器:
代理对象将方法调用转发给一个特定的处理器,这取决于所使用的代理类型。对于
JDK
动态代理,这个处理器是
InvocationHandler
;对于
CGLIB
代理,是
MethodInterceptor
。
执行额外操作(调用前):
在实际执行目标对象的方法之前,处理器有机会执行一些额外的操作,例如日志记录、安全检查或事务管理等。
调用目标对象的方法:
处理器在必要时直接调用目标对象的方法。在
JDK
动态代理中,这通常通过反射实现;而在
CGLIB
中,可以通过
MethodProxy.invokeSuper
方法调用。
执行额外操作(调用后):
方法调用完成后,处理器再次有机会执行额外操作,比如修改返回值、记录执行时间或进行事务的提交或回滚。
返回给客户端:
最终,方法的返回值被通过代理对象返回给客户端。
5. JDK动态代理 VS CGLIB动态代理对比
JDK动态代理
JDK
动态代理是
Java
自带的代理机制,它直接使用反射
API
来调用方法。
优点:
缺点:
CGLIB动态代理
CGLIB
(
Code Generation Library
)通过在运行时生成被代理对象的子类来实现代理。
优点:
缺点:
性能比较
选择建议
如果类已经实现了接口,或者希望强制使用接口编程,那么
JDK
动态代理是一个好选择。
如果需要代理没有实现接口的类,或者对性能有较高的要求,特别是在代理方法的调用上,
CGLIB
可能是更好的选择。
在现代的
Java
应用中,很多框架(如
Spring
)都提供了对这两种代理方式的透明支持,并且可以根据实际情况自动选择使用哪一种。例如,
Spring AOP
默认会使用
JDK
动态代理,但如果遇到没有实现接口的类,它会退回到
CGLIB
。
6. 动态代理的实际应用场景
面向切面编程(
AOP
):
事务管理:
权限控制和安全性:
延迟加载:
服务接口调用的拦截和增强:
在现代框架中的应用
Spring框架:
Spring
的
AOP
模块和事务管理广泛使用了动态代理技术。根据目标对象的类型(是否实现接口),
Spring
可以自动选择
JDK
动态代理或
CGLIB
代理。
Hibernate:
Hibernate
使用动态代理技术实现懒加载,代理实体类的关联对象,在实际访问这些对象时才从数据库中加载它们的数据。
MyBatis:
MyBatis
框架使用动态代理技术映射接口和
SQL
语句,允许开发者通过接口直接与数据库交互,而无需实现类。
欢迎一键三连~
有问题请留言,大家一起探讨学习
点击关注,第一时间了解华为云新鲜技术~