用户云卷云舒 发表于 2024-5-17 15:54:08

Spring开发:动态代理的艺术与实践

本文分享自华为云社区《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;

interface HelloWorld {
    void sayHello();
}

class HelloWorldImpl implements HelloWorld {
    public void sayHello() {
      System.out.println("Hello world!");
    }
}

public class DemoApplication {
    public static void main(String[] args) {
      HelloWorldImpl realObject = new HelloWorldImpl();
      HelloWorld proxyInstance = (HelloWorld) Proxy.newProxyInstance(
                HelloWorldImpl.class.getClassLoader(), // 使用目标类的类加载器
                new Class[]{HelloWorld.class}, // 代理类需要实现的接口列表
                new InvocationHandler() {
                  @Override
                  public Object 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");
                        return result;
                  }
                });

      proxyInstance.sayHello();
    }
}运行结果如下:
https://static001.geekbang.org/infoq/af/afc119e4f62137634fe756b8371a2364.png
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

[*]定义:args 是一个对象数组,包含了调用代理方法时转达给方法的参数值。如果被调用的方法没有参数,args 将会是 null 或者空数组。
[*]作用:args 允许在 invoke 方法内部转达参数给实际要实行的方法。这意味着可以在动态代理中不仅控制是否调用某个方法,还可以修改调用该方法时使用的参数。
2.2 不同方法分别代理

我们继续通过扩展 HelloWorld 接口来包含多个方法,并通过JDK动态代理演示权限控制和功能开关操作的一种实现方式
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface HelloWorld {
    void sayHello();
    void sayGoodbye();
}

class HelloWorldImpl implements HelloWorld {
    public void sayHello() {
      System.out.println("Hello world!");
    }

    public void sayGoodbye() {
      System.out.println("Goodbye world!");
    }
}

public class DemoApplication {
    public static void main(String[] args) {
      HelloWorld realObject = new HelloWorldImpl();
      HelloWorld proxyInstance = (HelloWorld) Proxy.newProxyInstance(
                HelloWorldImpl.class.getClassLoader(),
                new Class[]{HelloWorld.class},
                new InvocationHandler() {
                  // 添加一个简单的权限控制演示
                  private boolean accessAllowed = true;

                  // 简单的功能开关
                  private boolean goodbyeFunctionEnabled = true;

                  @Override
                  public Object 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());

                        return result;
                  }
                });
      // 正常执行
      proxyInstance.sayHello();

      // 可以根据goodbyeFunctionEnabled变量决定是否执行
      proxyInstance.sayGoodbye();
    }
}运行如下:
https://static001.geekbang.org/infoq/09/09efceed96dee0e5bcb26a076fe589ff.png
如果accessAllowed 变量为false:
https://static001.geekbang.org/infoq/4d/4d223f3ce1122c9b5745aacd3c1d24aa.png
如果goodbyeFunctionEnabled 变量为false:
https://static001.geekbang.org/infoq/bf/bf4bc910b355781c606ce23c09c7b66a.png
在这个例子中:

[*]权限控制:通过检查 accessAllowed 变量,我们可以模拟简单的权限控制。如果没有权限,可以直接返回或抛出非常,克制实行方法。
[*]功能开关:通过检查方法名称和 goodbyeFunctionEnabled 变量,我们可以控制 sayGoodbye 方法是否被实行。这可以用来根据配置启用或禁用特定功能。
这个例子展示了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;

interface HelloWorld {
    void sayHello();
}

class HelloWorldImpl implements HelloWorld {
    public void sayHello() {
      System.out.println("Hello world!");
    }
}

public class DemoApplication {
    public static void main(String[] args) {
      HelloWorld realObject = new HelloWorldImpl();
      HelloWorld proxyInstance = (HelloWorld) Proxy.newProxyInstance(
                HelloWorldImpl.class.getClassLoader(),
                new Class[]{HelloWorld.class},
                new AdvancedInvocationHandler(realObject));

      // 模拟多次调用以观察限流和熔断效果
      for (int i = 0; i < 10; i++) {
            proxyInstance.sayHello();
      }
    }
    static class AdvancedInvocationHandler implements InvocationHandler {
      private final Object target;
      private AtomicInteger requestCount = new AtomicInteger(0);
      private AtomicLong lastTimestamp = new AtomicLong(System.currentTimeMillis());
      private volatile boolean circuitBreakerOpen = false;
      private final long cooldownPeriod = 10000; // 冷却时间10秒

      public AdvancedInvocationHandler(Object target) {
            this.target = target;
      }

      @Override
      public Object 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());

            return result;
      }
    }
}https://static001.geekbang.org/infoq/6a/6a166b0fc2555766a2add617cc3cd218.png
在这个扩展示例中,我们实现了:

[*]熔断机制:通过一个简单的计数器和时间戳来模拟。如果在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;
class HelloWorld {
    public void sayHello() {
      System.out.println("Hello world!");
    }
}

public class DemoApplication {
    public static void main(String[] args) {
      Enhancer enhancer = new Enhancer();
      // 设置需要代理的类
      enhancer.setSuperclass(HelloWorld.class);

      enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object 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");
                return result;
            }
      });

      HelloWorld proxy = (HelloWorld) enhancer.create(); // 创建代理对象
      proxy.sayHello(); // 通过代理对象调用方法
    }
}运行结果如下:
https://img2024.cnblogs.com/blog/2030258/202404/2030258-20240416151836839-1195787311.png
 
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方法实际上是尝试在转达的对象上调用方法,如果该对象是代理对象,则调用会再次被拦截,造成无限循环。

[*]在JDK动态代理中,确保调用method.invoke时使用的是目的对象,而不是代理对象。
[*]在CGLIB代理中,使用MethodProxy.invokeSuper而不是MethodProxy.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;
class HelloWorldImpl {
    public void sayHello() {
      System.out.println("Hello world!");
    }

    public void sayGoodbye() {
      System.out.println("Goodbye world!");
    }
}
public class DemoApplication {
    public static void main(String[] args) {
      Enhancer enhancer = new Enhancer();
      enhancer.setSuperclass(HelloWorldImpl.class); // 设置被代理的类
      enhancer.setCallback(new MethodInterceptor() {
            // 添加一个简单的权限控制演示
            private boolean accessAllowed = true;

            // 简单的功能开关
            private boolean goodbyeFunctionEnabled = true;

            @Override
            public Object 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());

                return result;
            }
      });

      HelloWorldImpl proxyInstance = (HelloWorldImpl) enhancer.create(); // 创建代理对象
      proxyInstance.sayHello(); // 正常执行
      proxyInstance.sayGoodbye(); // 可以根据goodbyeFunctionEnabled变量决定是否执行
    }
}运行结果如下:
https://static001.geekbang.org/infoq/7f/7f2d820fe1a6238d5fb92f80d67f7973.png
我们需要注意几点更改:

[*]因为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;

class HelloWorld {
    void sayHello() {
      System.out.println("Hello world!");
    }
}

public class DemoApplication {
    public static void main(String[] args) {
      HelloWorld realObject = new HelloWorld();
      HelloWorld proxyInstance = (HelloWorld) createProxy(realObject);

      // 模拟多次调用以观察限流和熔断效果
      for (int i = 0; i < 10; i++) {
            proxyInstance.sayHello();
      }
    }

    public static Object createProxy(final Object realObject) {
      Enhancer enhancer = new Enhancer();
      enhancer.setSuperclass(HelloWorld.class);
      enhancer.setCallback(new AdvancedMethodInterceptor(realObject));
      return enhancer.create();
    }

    static class AdvancedMethodInterceptor implements MethodInterceptor {
      private final Object target;
      private final AtomicInteger requestCount = new AtomicInteger(0);
      private final AtomicLong lastTimestamp = new AtomicLong(System.currentTimeMillis());
      private volatile boolean circuitBreakerOpen = false;
      private final long cooldownPeriod = 10000; // 冷却时间10秒

      public AdvancedMethodInterceptor(Object target) {
            this.target = target;
      }

      @Override
      public Object 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());

            return result;
      }
    }
}运行结果
https://mp.toutiao.com/mp/agw/article_material/open_image/get?code=NDk3YzU4MzU1NGZhM2VhZGVkNDgwYWIxYzM1M2YxNTQsMTcxMzI1MTQ5MjUwMA==在这个改写中,我们使用CGLIB的Enhancer和MethodInterceptor来代替了JDK的Proxy和InvocationHandler。MethodInterceptor的intercept方法与InvocationHandler的invoke方法在概念上是相似的,但它使用MethodProxy的invokeSuper方法来调用原始类的方法,而不是使用反射。这允许CGLIB在运行时天生代理类的字节码,而不是依赖于反射,从而提高了性能。别的,circuitBreakerOpen被声明为volatile,是确保其在多线程环境中的可见性。
4. 动态代理图示

https://mp.toutiao.com/mp/agw/article_material/open_image/get?code=NWU3NDdmYjUwZmQzYTM4ODQ4NTA1MWNlMDNjZTc5MTQsMTcxMzI1MTUwNzY1NQ==方法调用拦截:
客户端通过代理对象调用方法,此时方法调用被代理对象拦截。
转发给处理器或方法拦截器:
代理对象将方法调用转发给一个特定的处理器,这取决于所使用的代理类型。对于JDK动态代理,这个处理器是InvocationHandler;对于CGLIB代理,是MethodInterceptor。
实行额外操作(调用前):
在实际实行目的对象的方法之前,处理器有时机实行一些额外的操作,例如日志记录、安全检查或事务管理等。
调用目的对象的方法:
处理器在必要时直接调用目的对象的方法。在JDK动态代理中,这通常通过反射实现;而在CGLIB中,可以通过MethodProxy.invokeSuper方法调用。
实行额外操作(调用后):
方法调用完成后,处理器再次有时机实行额外操作,比如修改返回值、记录实行时间或进行事务的提交或回滚。
返回给客户端:
最终,方法的返回值被通过代理对象返回给客户端。
5. JDK动态代理 VS CGLIB动态代理对比

JDK动态代理
JDK动态代理是Java自带的代理机制,它直接使用反射API来调用方法。
长处:

[*]无需第三方依赖:作为Java标准API的一部分,使用JDK动态代理不需要添加额外的库或依赖。
[*]接口导向:强制使用接口进行代理,这符合面向接口编程的原则,有助于保持代码的清晰和灵活。
缺点:

[*]仅限接口:只能代理实现了接口的类,这在某些环境下限制了它的使用。
[*]性能开销:由于使用反射API进行方法调用,大概会有肯定的性能开销,尤其是在大量调用时。
CGLIB动态代理
CGLIB(Code Generation Library)通过在运行时天生被代理对象的子类来实现代理。
长处:

[*]不需要接口:可以代理没有实现任何接口的类,这提供了更大的灵活性。
[*]性能较好:通常认为CGLIB的性能比JDK动态代理要好,特别是在代理方法的调用上,因为CGLIB使用了字节码天生技术,减少了使用反射的需要。
缺点:

[*]第三方库:需要添加CGLIB库作为项目依赖。
[*]无法代理final方法:由于CGLIB是通过天生子类的方式来代理的,以是无法代理那些被声明为final的方法。
性能比较

[*]调用速率:CGLIB在代理方法调用方面通常比JDK动态代理更快。这是因为CGLIB通过直接操作字节码来天生新的类,克制了反射带来的性能开销。
[*]启动性能:CGLIB在天生代理对象时大概会比JDK动态代理慢,因为它需要在运行时天生新的字节码。如果代理对象在应用启动时就被创建,这大概会略微影响启动时间。
选择建议

[*]如果类已经实现了接口,或者希望强制使用接口编程,那么JDK动态代理是一个好选择。
[*]如果需要代理没有实现接口的类,或者对性能有较高的要求,特别是在代理方法的调用上,CGLIB大概是更好的选择。
[*]在现代的Java应用中,很多框架(如Spring)都提供了对这两种代理方式的透明支持,并且可以根据实际环境自动选择使用哪一种。例如,Spring AOP默认会使用JDK动态代理,但如果遇到没有实现接口的类,它会退回到CGLIB。
6. 动态代理的实际应用场景

面向切面编程(AOP):

[*]问题解决:在不改变原有业务逻辑代码的环境下,为程序动态地添加额外的行为(如日志记录、性能监测、事务管理等)。
[*]应用实例:Spring AOP 使用动态代理为方法调用提供了声明式事务管理、安全性检查和日志记录等服务。根据目的对象是否实现接口,Spring AOP可以选择使用JDK动态代理或CGLIB代理。
事务管理:

[*]问题解决:自动化处理数据库事务的界限,如开始、提交或回滚事务。
[*]应用实例:Spring框架中的声明式事务管理使用代理技术拦截那些被@Transactional注解标记的类或方法,确保方法实行在正确的事务管理下进行。
权限控制和安全性:

[*]问题解决:在实行敏感操作之前自动检查用户权限,确保只有拥有充足权限的用户才气实行某些操作。
[*]应用实例:企业应用中,使用代理技术拦截用户的请求,进行权限验证后才允许访问特定的服务或实行操作。
延迟加载:

[*]问题解决:对象的某些属性大概加载成本较高,通过代理技术,可以在实际使用这些属性时才进行加载。
[*]应用实例:Hibernate和其他ORM框架使用代理技术实现了延迟加载(懒加载),以提高应用程序的性能和资源利用率。
服务接口调用的拦截和增强:

[*]问题解决:对第三方库或已有服务进行包装,添加额外的逻辑,如缓存结果、参数校验等。
[*]应用实例:在微服务架构中,可以使用代理技术对服务客户端进行增强,实现如重试、熔断、限流等逻辑。
在现代框架中的应用

[*]Spring框架:Spring的AOP模块和事务管理广泛使用了动态代理技术。根据目的对象的类型(是否实现接口),Spring可以自动选择JDK动态代理或CGLIB代理。
[*]Hibernate:Hibernate使用动态代理技术实现懒加载,代理实体类的关联对象,在实际访问这些对象时才从数据库中加载它们的数据。
[*]MyBatis:MyBatis框架使用动态代理技术映射接口和SQL语句,允许开发者通过接口直接与数据库交互,而无需实现类。
接待一键三连~

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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Spring开发:动态代理的艺术与实践