Spring高手之路17——动态代理的艺术与实践

文章目录

  • 1. 背景
  • 2. JDK动态代理
    • 2.1 定义和演示
    • 2.2 不同方法分别代理
    • 2.3 熔断限流和日志监控
  • 3. CGLIB动态代理
    • 3.1 定义和演示
    • 3.2 不同方法分别代理(对比JDK动态代理写法)
    • 3.3 熔断限流和日志监控(对比JDK动态代理写法)
  • 4. 动态代理图示
  • 5. JDK动态代理 VS CGLIB动态代理对比
  • 6. 动态代理的实际应用场景

1. 背景

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

2. JDK动态代理

2.1 定义和演示

  JDK动态代理是Java语言提供的一种基于接口的代理机制,允许开发者在运行时动态地创建代理对象,而无需为每个类编写具体的代理实现。

  这种机制主要通过 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口实现。下面是JDK动态代理的核心要点和如何使用它们的概述。

使用步骤

  1. 定义接口:首先定义一个或多个接口,代理对象将实现这些接口。

  2. 实现接口:创建一个类,它实现上述接口,提供具体的实现逻辑。

  3. 创建 InvocationHandler 实现:定义一个 InvocationHandler 的实现,这个实现中的 invoke 方法可以包含自定义逻辑。

  4. 创建代理对象:使用 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() {@Overridepublic 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();}
}

运行结果如下:

在这里插入图片描述

  InvocationHandler 是动态代理的核心接口之一,当我们使用动态代理模式创建代理对象时,任何对代理对象的方法调用都会被转发到一个实现了 InvocationHandler 接口的实例的 invoke 方法上

  我们经常看到InvocationHandler 动态代理的匿名内部类,这会在代理对象的相应方法被调用时执行。具体地说,每当对代理对象执行方法调用时,调用的方法不会直接执行,而是转发到实现了InvocationHandlerinvoke 方法上。在这个 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 方法是在代理实例上调用方法时被自动调用的。通过这种方式可以在实际的方法执行前后加入自定义的逻辑,比如日志记录、权限检查等。

  • methodmethod 是一个 java.lang.reflect.Method 类的实例,代表了正在被调用的方法。在 invoke 方法中,这个对象用来标识代理对象上被调用的具体方法。

  注意:如果尝试直接在invoke方法内部使用method.invoke(proxy, args)调用代理对象的方法,而不是调用原始目标对象的方法,则会导致无限循环。这是因为调用proxy实例上的方法会再次被代理拦截,从而无限调用invoke方法,传参可别传错了。

  • invokeMethod 类的 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;@Overridepublic 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();}
}

运行如下:

在这里插入图片描述

如果accessAllowed 变量为false

在这里插入图片描述

如果goodbyeFunctionEnabled 变量为false

在这里插入图片描述

在这个例子中:

  • 权限控制:通过检查 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;}@Overridepublic 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;}}
}

在这里插入图片描述

在这个扩展示例中,我们实现了:

  • 熔断机制:通过一个简单的计数器和时间戳来模拟。如果在10秒内对任一方法的调用次数超过5次,我们就"打开"熔断器,阻止进一步的方法调用。在实际应用中,熔断逻辑可能更加复杂,可能包括错误率的检查、调用延迟的监控等。

  • 限流:这里使用的限流策略很简单,通过计数和时间戳来判断是否在短时间内请求过多。在更复杂的场景中,可以使用令牌桶或漏桶算法等更高级的限流策略。

  • 日志监控:在方法调用前后打印日志,这对于监控系统的行为和性能是非常有用的。在实际项目中,这些日志可以集成到日志管理系统中,用于问题诊断和性能分析。

  通过在 invoke 方法中加入这些逻辑,我们能够在不修改原有业务代码的情况下,为系统添加复杂的控制和监控功能。如果到达流量阈值或系统处于熔断状态,可以阻止对后端服务的进一步调用,直接返回一个默认值或错误响应,避免系统过载。

3. CGLIB动态代理

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

3.1 定义和演示

工作原理

  CGLIB通过继承目标类并在运行时生成子类来实现动态代理。代理类覆盖了目标类的非final方法,并在调用方法前后提供了注入自定义逻辑的能力。这种方法的一个关键优势是它不需要目标对象实现任何接口。

使用CGLIB的步骤

  1. 添加CGLIB依赖:首先,需要在项目中添加CGLIB库的依赖。
    如果使用Maven,可以添加如下依赖到pom.xml中:
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version> <!-- 目前最新的版本 -->
</dependency>
  1. 创建MethodInterceptor:实现MethodInterceptor接口,这是CGLIB提供的回调类型,用于定义方法调用的拦截逻辑。

  2. 生成代理对象:使用Enhancer类来创建代理对象。EnhancerCGLIB中用于生成新类的类。

改造一下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() {@Overridepublic 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(); // 通过代理对象调用方法}
}

运行结果如下:

在这里插入图片描述

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;@Overridepublic 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变量决定是否执行}
}

运行结果如下:

在这里插入图片描述

我们需要注意几点更改:

  1. 因为CGLIB不是基于接口的代理,而是通过生成目标类的子类来实现代理,所以我们不再需要接口HelloWorld

  2. 我们将使用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;}@Overridepublic 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;}}
}

运行结果

在这里插入图片描述

  在这个改写中,我们使用CGLIBEnhancerMethodInterceptor来代替了JDKProxyInvocationHandlerMethodInterceptorintercept方法与InvocationHandlerinvoke方法在概念上是相似的,但它使用MethodProxyinvokeSuper方法来调用原始类的方法,而不是使用反射。这允许CGLIB在运行时生成代理类的字节码,而不是依赖于反射,从而提高了性能。此外,circuitBreakerOpen被声明为volatile,是确保其在多线程环境中的可见性。

4. 动态代理图示

在这里插入图片描述

  1. 方法调用拦截:

客户端通过代理对象调用方法,此时方法调用被代理对象拦截。

  1. 转发给处理器或方法拦截器:

代理对象将方法调用转发给一个特定的处理器,这取决于所使用的代理类型。对于JDK动态代理,这个处理器是InvocationHandler;对于CGLIB代理,是MethodInterceptor

  1. 执行额外操作(调用前):

在实际执行目标对象的方法之前,处理器有机会执行一些额外的操作,例如日志记录、安全检查或事务管理等。

  1. 调用目标对象的方法:

处理器在必要时直接调用目标对象的方法。在JDK动态代理中,这通常通过反射实现;而在CGLIB中,可以通过MethodProxy.invokeSuper方法调用。

  1. 执行额外操作(调用后):

方法调用完成后,处理器再次有机会执行额外操作,比如修改返回值、记录执行时间或进行事务的提交或回滚。

  1. 返回给客户端:

最终,方法的返回值被通过代理对象返回给客户端。

5. JDK动态代理 VS CGLIB动态代理对比

JDK动态代理

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

优点:

  • 无需第三方依赖:作为Java标准API的一部分,使用JDK动态代理不需要添加额外的库或依赖。

  • 接口导向:强制使用接口进行代理,这符合面向接口编程的原则,有助于保持代码的清晰和灵活。

缺点

  • 仅限接口:只能代理实现了接口的类,这在某些情况下限制了它的使用。

  • 性能开销:由于使用反射API进行方法调用,可能会有一定的性能开销,尤其是在大量调用时。

CGLIB动态代理

CGLIBCode Generation Library)通过在运行时生成被代理对象的子类来实现代理。

优点

  • 不需要接口:可以代理没有实现任何接口的类,这提供了更大的灵活性。

  • 性能较好:通常认为CGLIB的性能比JDK动态代理要好,特别是在代理方法的调用上,因为CGLIB使用了字节码生成技术,减少了使用反射的需要。

缺点

  • 第三方库:需要添加CGLIB库作为项目依赖。

  • 无法代理final方法:由于CGLIB是通过生成子类的方式来代理的,所以无法代理那些被声明为final的方法。

性能比较

  • 调用速度:CGLIB在代理方法调用方面通常比JDK动态代理更快。这是因为CGLIB通过直接操作字节码来生成新的类,避免了反射带来的性能开销。

  • 启动性能:CGLIB在生成代理对象时可能会比JDK动态代理慢,因为它需要在运行时生成新的字节码。如果代理对象在应用启动时就被创建,这可能会略微影响启动时间。

选择建议

  • 如果类已经实现了接口,或者希望强制使用接口编程,那么JDK动态代理是一个好选择。

  • 如果需要代理没有实现接口的类,或者对性能有较高的要求,特别是在代理方法的调用上,CGLIB可能是更好的选择。

  • 在现代的Java应用中,很多框架(如Spring)都提供了对这两种代理方式的透明支持,并且可以根据实际情况自动选择使用哪一种。例如,Spring AOP默认会使用JDK动态代理,但如果遇到没有实现接口的类,它会退回到CGLIB

6. 动态代理的实际应用场景

  1. 面向切面编程(AOP):
  • 问题解决:在不改变原有业务逻辑代码的情况下,为程序动态地添加额外的行为(如日志记录、性能监测、事务管理等)。

  • 应用实例:Spring AOP 使用动态代理为方法调用提供了声明式事务管理、安全性检查和日志记录等服务。根据目标对象是否实现接口,Spring AOP可以选择使用JDK动态代理或CGLIB代理。

  1. 事务管理:
  • 问题解决:自动化处理数据库事务的边界,如开始、提交或回滚事务。

  • 应用实例:Spring框架中的声明式事务管理使用代理技术拦截那些被@Transactional注解标记的类或方法,确保方法执行在正确的事务管理下进行。

  1. 权限控制和安全性:
  • 问题解决:在执行敏感操作之前自动检查用户权限,确保只有拥有足够权限的用户才能执行某些操作。

  • 应用实例:企业应用中,使用代理技术拦截用户的请求,进行权限验证后才允许访问特定的服务或执行操作。

  1. 延迟加载:
  • 问题解决:对象的某些属性可能加载成本较高,通过代理技术,可以在实际使用这些属性时才进行加载。

  • 应用实例:Hibernate和其他ORM框架使用代理技术实现了延迟加载(懒加载),以提高应用程序的性能和资源利用率。

  1. 服务接口调用的拦截和增强:
  • 问题解决:对第三方库或已有服务进行包装,添加额外的逻辑,如缓存结果、参数校验等。

  • 应用实例:在微服务架构中,可以使用代理技术对服务客户端进行增强,实现如重试、熔断、限流等逻辑。

在现代框架中的应用

  • Spring框架SpringAOP模块和事务管理广泛使用了动态代理技术。根据目标对象的类型(是否实现接口),Spring可以自动选择JDK动态代理或CGLIB代理。

  • HibernateHibernate使用动态代理技术实现懒加载,代理实体类的关联对象,在实际访问这些对象时才从数据库中加载它们的数据。

  • MyBatisMyBatis框架使用动态代理技术映射接口和SQL语句,允许开发者通过接口直接与数据库交互,而无需实现类。


欢迎一键三连~

有问题请留言,大家一起探讨学习

----------------------Talk is cheap, show me the code-----------------------

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/308952.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring源码刨析之配置文件的解析和bean的创建以及生命周期

public void test1(){XmlBeanFactory xmlBeanFactory new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));user u xmlBeanFactory.getBean("user",org.xhpcd.user.class);// System.out.println(u.getStu());}先介绍一个类XmlBeanFac…

196算法之谜在 JSP 中使用内置对象 request 获取 form 表单的文本框 text 提交的数据。

(1&#xff09;编写 inputNumber . jsp &#xff0c;该页面提供一个 form 表单&#xff0c;该 form 表单提供一个文本框 text &#xff0c;用于用户输入一个正整数&#xff0c;用户在 form 表单中输入的数字&#xff0c;单击 submit 提交键将正整数提交给 huiwenNumber . jsp 页…

LeetCode 57—— 插入区间

阅读目录 1. 题目2. 解题思路3. 代码实现 1. 题目 2. 解题思路 第一步&#xff0c;我们先寻找新区间和原始区间列表的重叠部分。 假设新区间为 [ x 1 , x 2 ] [x_1, x_2] [x1​,x2​]&#xff0c;原始区间列表中的其中一个区间为 [ y 1 , y 2 ] [y_1, y_2] [y1​,y2​]&…

产生死锁的四个必要条件

产生死锁的四个必要条件 互斥使用: 一个资源每次只能被一个线程使用。这意味着如果一个线程已经获取了某个资源&#xff08;比如锁&#xff09;&#xff0c;那么其他线程就必须等待&#xff0c;直到该线程释放资源。 不可抢占: 已经获得资源的线程在释放资源之前&#xff0c;不…

Google Imagen 2对比OpenAI的Dall-E 3 - 同一提示,不同结果

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

咸鱼之王_手游_开服搭建架设_内购修复无bug运营版

视频演示 咸鱼之王_手游_开服 游戏管理后台界面 源码获取在文章末尾 源码获取在文章末尾 源码获取在文章末尾 或者直接下面 https://githubs.xyz/y28.html 1.安装宝塔 yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh &…

【C++】深度解析---赋值运算符重载(小白一看就懂!!)

目录 一、前言 二、 运算符重载 &#x1f34e;运算符重载 ① 概念引入 ② 语法明细 ③ 练习巩固 ④ 代码展示 &#x1f347;赋值运算符重载 ① 语法说明及注意事项 ② 默认的赋值运算符重载 ③ 值运算符不能重载成全局函数&#xff01; 三、总结 四、共勉 一、前言…

centos 7.9 nginx本地化安装,把镜像改成阿里云

1.把centos7.9系统切换到阿里云的镜像源 1.1.先备份 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup1.2.下载新的CentOS-Base.repo配置文件 wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo特别…

本地web项目启起来后,无法在浏览器(chrome)看到源码,从而无法打断点;Framework Ignore list

问题描述 本地web项目启起来后&#xff0c;无法在浏览器(chrome)看到源码&#xff0c;从而无法打断点 其他浏览器没看&#xff0c;开发环境一致专注于chrome&#xff08;其余浏览器有测试同事提缺陷了&#xff0c;才会去看&#xff09;&#xff0c;其余浏览器有没有这个问题&…

linux 内存寻址

&#xff08;持续更新&#xff09; 相关概念 查看的书籍为 深入linux内核 内存地址 当使用80x86&#xff08;32位&#xff09;微处理器时&#xff0c;一般分为三种不同的地址&#xff1a; 逻辑地址 包含在机器语言指令中用来指定一个操作数或一条指令的地址。每一个逻辑地址…

C#简单工厂模式的实现

using System.Diagnostics.Metrics; using System.Runtime.InteropServices; using static 手写工厂模式.Program;namespace 手写工厂模式 {internal class Program{public interface eats {void eat();}//定义了一个接口public class rice : eats{public void eat() {Console.…

neo4j-01

Neo4j是&#xff1a; 开源的&#xff08;社区版开源免费&#xff09;无模式&#xff08;不用预设数据的格式&#xff0c;数据更加灵活&#xff09;noSQL&#xff08;非关系型数据库&#xff0c;数据更易拓展&#xff09;图数据库&#xff08;使用图这种数据结构作为数据存储方…

Flutter第七弹 网格列表GridView

1) Flutter提供了网格列表&#xff0c;怎么设置列数&#xff1f; 2&#xff09;怎么初始化每个列表项Item&#xff1f; 一、GridView简介 Flutter也存在网格列表组建GridView&#xff0c;用于展示多行多列的列表。 1.1 GridView构建 采用GridView.count() 进行构建 1.2 Gr…

HarmonyOS实战开发-自定义分享

介绍 自定义分享主要是发送方将文本&#xff0c;链接&#xff0c;图片三种类型分享给三方应用,同时能够在三方应用中展示。本示例使用数据请求 实现网络资源的获取&#xff0c;使用屏幕截屏 实现屏幕的截取&#xff0c;使用文件管理 实现对文件&#xff0c;文件目录的管理&…

5.9 mybatis之callSettersOnNulls作用

文章目录 1. 当callSettersOnNullstrue时2. 当callSettersOnNullsfalse时 在mybatis的settings配置参数中有个callSettersOnNulls参数&#xff0c;官方解释为&#xff1a;指定当结果集中值为 null 的时候是否调用映射对象的 setter&#xff08;map 对象时为 put&#xff09;方法…

Collection与数据结构 二叉树(三):二叉树精选OJ例题(下)

1.二叉树的分层遍历 OJ链接 上面这道题是分层式的层序遍历,每一层有哪些结点都很明确,我们先想一想普通的层序遍历怎么做 /*** 层序遍历* param root*/public void levelOrder1(Node root){Queue<Node> queue new LinkedList<>();queue.offer(root);while (!qu…

Geeker-Admin:基于Vue3.4、TypeScript、Vite5、Pinia和Element-Plus的开源后台管理框架

Geeker-Admin&#xff1a;基于Vue3.4、TypeScript、Vite5、Pinia和Element-Plus的开源后台管理框架 一、引言 随着技术的不断发展&#xff0c;前端开发领域也在不断演变。为了满足现代应用程序的需求&#xff0c;开发人员需要使用最新、最强大的工具和技术。Geeker-Admin正是…

Ubuntu 22上安装Anaconda3。下载、安装、验证详细教程

在Ubuntu 22上安装Anaconda3&#xff0c;你可以遵循以下步骤&#xff1a; 更新系统存储库&#xff1a; 打开终端并运行以下命令来更新系统存储库&#xff1a; sudo apt update安装curl包&#xff1a; 下载Anaconda安装脚本通常需要使用curl工具。如果系统中没有安装curl&#x…

流媒体的安全谁来保障

流媒体的安全谁来保障 说起媒体&#xff0c;我们马上就会想到报纸新闻、广播、电视。 其实所谓的流媒体同我们通常所指的媒体是不一样的&#xff0c; 它只是一个技术名词。流媒体到底是什么&#xff1f;能给我们的生活带来什么&#xff1f;跟小德一起来看看。 流媒体是什么&a…

OSI七层网络模型 —— 筑梦之路

在信息技术领域&#xff0c;OSI七层模型是一个经典的网络通信框架&#xff0c;它将网络通信分为七个层次&#xff0c;每一层都有其独特的功能和作用。为了帮助记忆这七个层次&#xff0c;有一个巧妙的方法&#xff1a;将每个层次的英文单词首字母组合起来&#xff0c;形成了一句…