ToB企服应用市场:ToB评测及商务社交产业平台

标题: Spring开发:动态代理的艺术与实践 [打印本页]

作者: 用户云卷云舒    时间: 2024-5-17 15:54
标题: Spring开发:动态代理的艺术与实践
本文分享自华为云社区《Spring高手之路17——动态代理的艺术与实践》,作者: 砖业洋__。
1. 背景

动态代理是一种强盛的设计模式,它允许开发者在运行时创建代理对象,用于拦截对真实对象的方法调用。这种技术在实现面向切面编程(AOP)、事务管理、权限控制等功能时特别有效,因为它可以在不修改原有代码布局的前提下,为程序动态地注入额外的逻辑。
2. JDK动态代理

2.1 定义和演示

JDK动态代理是Java语言提供的一种基于接口的代理机制,允许开发者在运行时动态地创建代理对象,而无需为每个类编写具体的代理实现。
这种机制主要通过 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口实现。下面是JDK动态代理的核心要点和怎样使用它们的概述。
使用步骤
用简单的例子来说明这个过程,全部代码如下:
  1. import java.lang.reflect.InvocationHandler;
  2. import java.lang.reflect.Method;
  3. import java.lang.reflect.Proxy;
  4. interface HelloWorld {
  5.     void sayHello();
  6. }
  7. class HelloWorldImpl implements HelloWorld {
  8.     public void sayHello() {
  9.         System.out.println("Hello world!");
  10.     }
  11. }
  12. public class DemoApplication {
  13.     public static void main(String[] args) {
  14.         HelloWorldImpl realObject = new HelloWorldImpl();
  15.         HelloWorld proxyInstance = (HelloWorld) Proxy.newProxyInstance(
  16.                 HelloWorldImpl.class.getClassLoader(), // 使用目标类的类加载器
  17.                 new Class[]{HelloWorld.class}, // 代理类需要实现的接口列表
  18.                 new InvocationHandler() {
  19.                     @Override
  20.                     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  21.                         // 在调用目标方法前可以插入自定义逻辑
  22.                         System.out.println("Before method call");
  23.                         // 调用目标对象的方法
  24.                         Object result = method.invoke(realObject, args);
  25.                         // 在调用目标方法后可以插入自定义逻辑
  26.                         System.out.println("After method call");
  27.                         return result;
  28.                     }
  29.                 });
  30.         proxyInstance.sayHello();
  31.     }
  32. }
复制代码
运行结果如下:

InvocationHandler 是动态代理的核心接口之一,当我们使用动态代理模式创建代理对象时,任何对代理对象的方法调用都会被转发到一个实现了 InvocationHandler 接口的实例的 invoke 方法上。
我们经常看到InvocationHandler 动态代理的匿名内部类,这会在代理对象的相应方法被调用时实行。具体地说,每当对代理对象实行方法调用时,调用的方法不会直接实行,而是转发到实现了InvocationHandler 的 invoke 方法上。在这个 invoke 方法内部,我们可以定义拦截逻辑、调用原始对象的方法、修改返回值等操作。
在这个例子中,当调用 proxyInstance.sayHello() 方法时,实际上实行的是 InvocationHandler 的匿名内部类中的 invoke 方法。这个方法中,我们可以在调用实际对象的 sayHello 方法前后添加自定义逻辑(比如这里的打印消息)。这就是动态代理和 InvocationHandler 的工作原理。
我们来看关键的一句代码
  1. Object result = method.invoke(realObject, args);
复制代码
在Java的动态代理中,method.invoke(realObject, args) 这句代码扮演着核心的脚色,因为它实现了代理对象方法调用的转发机制。下面分别解释一下这行代码的两个主要部分:method.invoke() 方法和 args 参数。
method.invoke(realObject, args)
注意:如果尝试直接在invoke方法内部使用method.invoke(proxy, args)调用代理对象的方法,而不是调用原始目的对象的方法,则会导致无限循环。这是因为调用proxy实例上的方法会再次被代理拦截,从而无限调用invoke方法,传参可别传错了。
args
2.2 不同方法分别代理

我们继续通过扩展 HelloWorld 接口来包含多个方法,并通过JDK动态代理演示权限控制和功能开关操作的一种实现方式
  1. import java.lang.reflect.InvocationHandler;
  2. import java.lang.reflect.Method;
  3. import java.lang.reflect.Proxy;
  4. interface HelloWorld {
  5.     void sayHello();
  6.     void sayGoodbye();
  7. }
  8. class HelloWorldImpl implements HelloWorld {
  9.     public void sayHello() {
  10.         System.out.println("Hello world!");
  11.     }
  12.     public void sayGoodbye() {
  13.         System.out.println("Goodbye world!");
  14.     }
  15. }
  16. public class DemoApplication {
  17.     public static void main(String[] args) {
  18.         HelloWorld realObject = new HelloWorldImpl();
  19.         HelloWorld proxyInstance = (HelloWorld) Proxy.newProxyInstance(
  20.                 HelloWorldImpl.class.getClassLoader(),
  21.                 new Class[]{HelloWorld.class},
  22.                 new InvocationHandler() {
  23.                     // 添加一个简单的权限控制演示
  24.                     private boolean accessAllowed = true;
  25.                     // 简单的功能开关
  26.                     private boolean goodbyeFunctionEnabled = true;
  27.                     @Override
  28.                     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  29.                         // 权限控制
  30.                         if (!accessAllowed) {
  31.                             System.out.println("Access denied");
  32.                             return null; // 在实际场景中,可以抛出一个异常
  33.                         }
  34.                         // 功能开关
  35.                         if (method.getName().equals("sayGoodbye") && !goodbyeFunctionEnabled) {
  36.                             System.out.println("Goodbye function is disabled");
  37.                             return null;
  38.                         }
  39.                         // 方法执行前的通用逻辑
  40.                         System.out.println("Before method: " + method.getName());
  41.                         // 执行方法
  42.                         Object result = method.invoke(realObject, args);
  43.                         // 方法执行后的通用逻辑
  44.                         System.out.println("After method: " + method.getName());
  45.                         return result;
  46.                     }
  47.                 });
  48.         // 正常执行
  49.         proxyInstance.sayHello();
  50.         // 可以根据goodbyeFunctionEnabled变量决定是否执行
  51.         proxyInstance.sayGoodbye();
  52.     }
  53. }
复制代码
运行如下:

如果accessAllowed 变量为false:

如果goodbyeFunctionEnabled 变量为false:

在这个例子中:
这个例子展示了JDK动态代理在实际应用中怎样进行方法级别的细粒度控制,同时保持代码的灵活性和可维护性。通过动态代理,我们可以在不修改原始类代码的环境下,为对象动态地添加额外的行为。
2.3 熔断限流和日志监控

为了更全面地展示JDK动态代理的本事,我们在先前的示例中添加熔断限流和日志监控的逻辑。这些是在高并发和分布式系统中常见的需求,可以通过动态代理以非侵入式的方式实现。
  1. import java.lang.reflect.InvocationHandler;
  2. import java.lang.reflect.Method;
  3. import java.lang.reflect.Proxy;
  4. import java.util.concurrent.atomic.AtomicInteger;
  5. import java.util.concurrent.atomic.AtomicLong;
  6. interface HelloWorld {
  7.     void sayHello();
  8. }
  9. class HelloWorldImpl implements HelloWorld {
  10.     public void sayHello() {
  11.         System.out.println("Hello world!");
  12.     }
  13. }
  14. public class DemoApplication {
  15.     public static void main(String[] args) {
  16.         HelloWorld realObject = new HelloWorldImpl();
  17.         HelloWorld proxyInstance = (HelloWorld) Proxy.newProxyInstance(
  18.                 HelloWorldImpl.class.getClassLoader(),
  19.                 new Class[]{HelloWorld.class},
  20.                 new AdvancedInvocationHandler(realObject));
  21.         // 模拟多次调用以观察限流和熔断效果
  22.         for (int i = 0; i < 10; i++) {
  23.             proxyInstance.sayHello();
  24.         }
  25.     }
  26.     static class AdvancedInvocationHandler implements InvocationHandler {
  27.         private final Object target;
  28.         private AtomicInteger requestCount = new AtomicInteger(0);
  29.         private AtomicLong lastTimestamp = new AtomicLong(System.currentTimeMillis());
  30.         private volatile boolean circuitBreakerOpen = false;
  31.         private final long cooldownPeriod = 10000; // 冷却时间10秒
  32.         public AdvancedInvocationHandler(Object target) {
  33.             this.target = target;
  34.         }
  35.         @Override
  36.         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  37.             long now = System.currentTimeMillis();
  38.             // 检查熔断器是否应该被重置
  39.             if (circuitBreakerOpen && (now - lastTimestamp.get() > cooldownPeriod)) {
  40.                 circuitBreakerOpen = false; // 重置熔断器
  41.                 requestCount.set(0); // 重置请求计数
  42.                 System.out.println("Circuit breaker has been reset.");
  43.             }
  44.             // 熔断检查
  45.             if (circuitBreakerOpen) {
  46.                 System.out.println("Circuit breaker is open. Blocking method execution for: " + method.getName());
  47.                 return null; // 在实际场景中,可以返回一个兜底的响应或抛出异常
  48.             }
  49.             // 限流检查
  50.             if (requestCount.incrementAndGet() > 5) {
  51.                 if (now - lastTimestamp.get() < cooldownPeriod) { // 10秒内超过5次请求,触发熔断
  52.                     circuitBreakerOpen = true;
  53.                     lastTimestamp.set(now); // 更新时间戳
  54.                     System.out.println("Too many requests. Opening circuit breaker.");
  55.                     return null; // 触发熔断时的处理
  56.                 } else {
  57.                     // 重置计数器和时间戳
  58.                     requestCount.set(0);
  59.                     lastTimestamp.set(now);
  60.                 }
  61.             }
  62.             // 执行实际方法
  63.             Object result = method.invoke(target, args);
  64.             // 方法执行后的逻辑
  65.             System.out.println("Executed method: " + method.getName());
  66.             return result;
  67.         }
  68.     }
  69. }
复制代码

在这个扩展示例中,我们实现了:
  通过在 invoke 方法中参加这些逻辑,我们能够在不修改原有业务代码的环境下,为系统添加复杂的控制和监控功能。如果到达流量阈值或系统处于熔断状态,可以克制对后端服务的进一步调用,直接返回一个默认值或错误响应,克制系统过载。
3. CGLIB动态代理

CGLIB(Code Generation Library)是一个强盛的高性能代码天生库,它在运行时动态天生新的类。与JDK动态代理不同,CGLIB能够代理那些没有实现接口的类。这使得CGLIB成为那些因为设计限制或其他原因不能使用接口的场景的理想选择。
3.1 定义和演示

工作原理
CGLIB通过继续目的类并在运行时天生子类来实现动态代理。代理类覆盖了目的类的非final方法,并在调用方法前后提供了注入自定义逻辑的本事。这种方法的一个关键优势是它不需要目的对象实现任何接口。
使用CGLIB的步骤
添加CGLIB依赖:首先,需要在项目中添加CGLIB库的依赖。
如果使用Maven,可以添加如下依赖到pom.xml中:
  1. <dependency>
  2.     <groupId>cglib</groupId>
  3.     <artifactId>cglib</artifactId>
  4.     <version>3.3.0</version>
  5. </dependency>
复制代码
创建MethodInterceptor:实现MethodInterceptor接口,这是CGLIB提供的回调类型,用于定义方法调用的拦截逻辑。
天生代理对象:使用Enhancer类来创建代理对象。Enhancer是CGLIB中用于天生新类的类。
改造一下1.1节的例子,可以对比看看,全部示例代码如下:
  1. import net.sf.cglib.proxy.Enhancer;
  2. import net.sf.cglib.proxy.MethodInterceptor;
  3. import net.sf.cglib.proxy.MethodProxy;
  4. class HelloWorld {
  5.     public void sayHello() {
  6.         System.out.println("Hello world!");
  7.     }
  8. }
  9. public class DemoApplication {
  10.     public static void main(String[] args) {
  11.         Enhancer enhancer = new Enhancer();
  12.         // 设置需要代理的类
  13.         enhancer.setSuperclass(HelloWorld.class);
  14.         enhancer.setCallback(new MethodInterceptor() {
  15.             @Override
  16.             public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable {
  17.                 System.out.println("Before method call");
  18.                 Object result = proxy.invokeSuper(obj, args); // 调用父类的方法
  19.                 System.out.println("After method call");
  20.                 return result;
  21.             }
  22.         });
  23.         HelloWorld proxy = (HelloWorld) enhancer.create(); // 创建代理对象
  24.         proxy.sayHello(); // 通过代理对象调用方法
  25.     }
  26. }
复制代码
运行结果如下:

 
CGLIB vs JDK动态代理
CGLIB是一个强盛的工具,特别实用于需要代理没有实现接口的类的场景。然而,选择JDK动态代理还是CGLIB主要取决于具体的应用场景和性能要求。
注意:在CGLIB中,如果使用MethodProxy.invoke(obj, args) ,而不是MethodProxy.invokeSuper(obj, args),并且obj是代理实例本身(CGLIB通过Enhancer创建的代理对象,而不是原始的被代理的目的对象),就会导致无限循环。invoke方法实际上是尝试在转达的对象上调用方法,如果该对象是代理对象,则调用会再次被拦截,造成无限循环。
3.2 不同方法分别代理(对比JDK动态代理写法)

我们改写1.2节的例子
  1. import net.sf.cglib.proxy.Enhancer;
  2. import net.sf.cglib.proxy.MethodInterceptor;
  3. import net.sf.cglib.proxy.MethodProxy;
  4. import java.lang.reflect.Method;
  5. class HelloWorldImpl {
  6.     public void sayHello() {
  7.         System.out.println("Hello world!");
  8.     }
  9.     public void sayGoodbye() {
  10.         System.out.println("Goodbye world!");
  11.     }
  12. }
  13. public class DemoApplication {
  14.     public static void main(String[] args) {
  15.         Enhancer enhancer = new Enhancer();
  16.         enhancer.setSuperclass(HelloWorldImpl.class); // 设置被代理的类
  17.         enhancer.setCallback(new MethodInterceptor() {
  18.             // 添加一个简单的权限控制演示
  19.             private boolean accessAllowed = true;
  20.             // 简单的功能开关
  21.             private boolean goodbyeFunctionEnabled = true;
  22.             @Override
  23.             public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
  24.                 // 权限控制
  25.                 if (!accessAllowed) {
  26.                     System.out.println("Access denied");
  27.                     return null; // 在实际场景中,可以抛出一个异常
  28.                 }
  29.                 // 功能开关
  30.                 if (method.getName().equals("sayGoodbye") && !goodbyeFunctionEnabled) {
  31.                     System.out.println("Goodbye function is disabled");
  32.                     return null;
  33.                 }
  34.                 // 方法执行前的通用逻辑
  35.                 System.out.println("Before method: " + method.getName());
  36.                 // 执行方法
  37.                 Object result = proxy.invokeSuper(obj, args);
  38.                 // 方法执行后的通用逻辑
  39.                 System.out.println("After method: " + method.getName());
  40.                 return result;
  41.             }
  42.         });
  43.         HelloWorldImpl proxyInstance = (HelloWorldImpl) enhancer.create(); // 创建代理对象
  44.         proxyInstance.sayHello(); // 正常执行
  45.         proxyInstance.sayGoodbye(); // 可以根据goodbyeFunctionEnabled变量决定是否执行
  46.     }
  47. }
复制代码
运行结果如下:

我们需要注意几点更改:
3.3 熔断限流和日志监控(对比JDK动态代理写法)

我们改写1.3节的例子
  1. import net.sf.cglib.proxy.Enhancer;
  2. import net.sf.cglib.proxy.MethodInterceptor;
  3. import net.sf.cglib.proxy.MethodProxy;
  4. import java.lang.reflect.Method;
  5. import java.util.concurrent.atomic.AtomicInteger;
  6. import java.util.concurrent.atomic.AtomicLong;
  7. class HelloWorld {
  8.     void sayHello() {
  9.         System.out.println("Hello world!");
  10.     }
  11. }
  12. public class DemoApplication {
  13.     public static void main(String[] args) {
  14.         HelloWorld realObject = new HelloWorld();
  15.         HelloWorld proxyInstance = (HelloWorld) createProxy(realObject);
  16.         // 模拟多次调用以观察限流和熔断效果
  17.         for (int i = 0; i < 10; i++) {
  18.             proxyInstance.sayHello();
  19.         }
  20.     }
  21.     public static Object createProxy(final Object realObject) {
  22.         Enhancer enhancer = new Enhancer();
  23.         enhancer.setSuperclass(HelloWorld.class);
  24.         enhancer.setCallback(new AdvancedMethodInterceptor(realObject));
  25.         return enhancer.create();
  26.     }
  27.     static class AdvancedMethodInterceptor implements MethodInterceptor {
  28.         private final Object target;
  29.         private final AtomicInteger requestCount = new AtomicInteger(0);
  30.         private final AtomicLong lastTimestamp = new AtomicLong(System.currentTimeMillis());
  31.         private volatile boolean circuitBreakerOpen = false;
  32.         private final long cooldownPeriod = 10000; // 冷却时间10秒
  33.         public AdvancedMethodInterceptor(Object target) {
  34.             this.target = target;
  35.         }
  36.         @Override
  37.         public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
  38.             long now = System.currentTimeMillis();
  39.             // 检查熔断器是否应该被重置
  40.             if (circuitBreakerOpen && (now - lastTimestamp.get() > cooldownPeriod)) {
  41.                 circuitBreakerOpen = false; // 重置熔断器
  42.                 requestCount.set(0); // 重置请求计数
  43.                 System.out.println("Circuit breaker has been reset.");
  44.             }
  45.             // 熔断检查
  46.             if (circuitBreakerOpen) {
  47.                 System.out.println("Circuit breaker is open. Blocking method execution for: " + method.getName());
  48.                 return null; // 在实际场景中,可以返回一个兜底的响应或抛出异常
  49.             }
  50.             // 限流检查
  51.             if (requestCount.incrementAndGet() > 5) {
  52.                 if (now - lastTimestamp.get() < cooldownPeriod) { // 10秒内超过5次请求,触发熔断
  53.                     circuitBreakerOpen = true;
  54.                     lastTimestamp.set(now); // 更新时间戳
  55.                     System.out.println("Too many requests. Opening circuit breaker.");
  56.                     return null; // 触发熔断时的处理
  57.                 } else {
  58.                     // 重置计数器和时间戳
  59.                     requestCount.set(0);
  60.                     lastTimestamp.set(now);
  61.                 }
  62.             }
  63.             // 执行实际方法
  64.             Object result = proxy.invokeSuper(obj, args); // 注意这里调用的是invokeSuper
  65.             // 方法执行后的逻辑
  66.             System.out.println("Executed method: " + method.getName());
  67.             return result;
  68.         }
  69.     }
  70. }
复制代码
运行结果
在这个改写中,我们使用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)通过在运行时天生被代理对象的子类来实现代理。
长处:
缺点:
性能比较
选择建议
6. 动态代理的实际应用场景

面向切面编程(AOP):
事务管理:
权限控制和安全性:
延迟加载:
服务接口调用的拦截和增强:
在现代框架中的应用
接待一键三连~

有问题请留言,大家一起探究学习
点击关注,第一时间了解华为云奇怪技术~
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4