图文并茂带你理解Java的代理模式

目录

  • Java的代理模式
    • 1、什么是代理模式?
    • 2、静态代理和动态代理
    • 3、JDK动态代理的局限性
    • 4、使用CGLIB代理机制完成未实现接口的类的代理
    • 5、JDK动态代理和CGLIB动态代理对比
    • 6、JDK动态代理为什么只能代理实现接口的类?

Java的代理模式

1、什么是代理模式?

代理模式( Proxy ),给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。 通常会通过代理对象来为原对象添加额外的功能。
代理模式属于结构型模式主要用于处理类或对象的组合。

上面是比较正式的书面释义,举个通俗点的例子来帮助理解:
周末你躺在床上饿了想吃火鸡面,但是你有点懒,你找你女朋友帮你泡好并端到你面前喂你吃,最后你把火鸡面吃完了,说了句宝宝你真好~
这个例子中:你吃了一包火鸡面,是你做的动作。 但是你请了你的女朋友帮你泡火鸡面,你的女朋友就可以理解为是你的代理,此时你女朋友这个代理帮你泡了火鸡面(代理对象添加了额外的功能)。为了完成你吃火鸡面这件事 ,最后你女朋友还得端到你面前喂你吃(这里的喂你吃火鸡面就相当于 (代理对象控制了原对象的引用) )。

2、静态代理和动态代理

根据名称我们可以猜测,静态的代理一定不够灵活,运用到代码中一定是耦合的。
而动态代理顾名思义是动态的,灵活的,运用到代码中是解耦的。

静态代理:
在静态代理中,代理类是在编译时就确定的,即在代码中显式地定义了代理类(需要手动编写一个代理类)。
代理类通常与被代理类实现相同的接口,并且在代理类的方法中调用被代理类的方法。
静态代理的一个缺点是每次添加一个新的功能,都需要创建一个代理类,这样会导致类的数量增加,并且会造成代码的冗余。
代理类通常需要直接引用被代理类,因此代理类对被代理类有一定的依赖关系。如果被代理类的接口发生变化,代理类也需要相应地进行修改,这增加了代码的耦合性。

静态代理实现步骤:

  • ①、定义一个接口用于被代理类实现,定义一个被代理类实现前面定义的接口;
  • ②、自定义一个代理类实现前面定义的接口 ,并重写接口中需要被代理的方法,在重写的方法中调用原方法(被代理类的方法)并自定义一些处理逻辑;
  • ③、通过代理类创建代理对象,使用代理对象替换原对象调用方法;

静态代理图示:
在这里插入图片描述

静态代理的代码示例:

public class TestStaticProxy {public static void main(String[] args) {YourBehavior you = new You();// 不使用代理you.eat("火鸡面");System.out.println("========================");// 使用代理YourBehavior yourGirlfriend = new YourGirlfriend(you);yourGirlfriend.eat("火鸡面");}
}/*** 行为接口 (代理类和被代理类都实现这个接口)* */
interface YourBehavior {/*** 吃* @param something 吃的东西*/void eat(String something);
}/*** 这个类代表你 (被代理类)* */
class You implements YourBehavior{@Overridepublic void eat(String something) {System.out.println("你吃:" + something);}
}/*** 这个类代表你女友 (代理类)* */
class YourGirlfriend implements YourBehavior{private You you;public YourGirlfriend() {}// 静态代理 必须依赖被代理类public YourGirlfriend(You you) {// 说明你的女朋友心里有你~this.you = you;}@Overridepublic void eat(String something) {System.out.println("帮你泡火鸡面,带到你面前");// 代理对象调用 原对象的方法you.eat("火鸡面");System.out.println("你说:谢谢宝~");}
}

运行结果:

吃:火鸡面
========================
帮你泡火鸡面,带到你面前
吃:火鸡面
你说:谢谢宝~

动态代理:
动态代理是在运行时生成的代理类,而不是在编译时确定的。Java中的动态代理机制主要依靠Java反射机制实现。
动态代理更加灵活,因为它可以在运行时决定要代理的对象及其行为,而不需要显式地为每个类编写代理类。
动态代理通常用于实现横切关注点(cross-cutting concerns AOP相关概念后续在Spring框架相关博客会详细介绍)的功能,
例如日志记录、性能监控、事务管理等。因为动态代理可以在运行时将这些功能动态地添加到方法调用中,而不需要修改原始类的代码。这就达到了解耦的目的。

Java中的动态代理主要是通过java.lang.reflect.Proxy类 和 InvocationHandler 接口实现的。
动态代理的代码示例:

JDK动态代理实现的步骤:

  • ①、定义一个接口用于被代理类实现,定义一个被代理类实现前面定义的接口;
  • ②、自定义一个类实现 InvocationHandler接口 并重写invoke方法,在 invoke 方法中调用原方法(被代理类的方法)并自定义一些处理逻辑;
  • ③、通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象,使用代理对象调用方法。 参数说明: loader 目标对象的类加载器,interfaces目标对象实现的全部接口,h 自定义的InvocationHandler实例;

JDK动态代理图示:
在这里插入图片描述

JDK动态代理代码示例:


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class TestDynamicProxy {public static void main(String[] args) {You you = new You();you.eat("火鸡面");System.out.println("========================");// 获取代理对象YourBehavior proxyInstance = (YourBehavior)ProxyFactory.getProxyObject(you);// 使用代理对象 调用方法proxyInstance.eat("火鸡面");}}/*** 行为接口 (代理类和被代理类都实现这个接口)*/
interface YourBehavior {/*** 吃** @param something 吃的东西*/void eat(String something);
}/*** 这个类代表你 (被代理类)*/
class You implements YourBehavior {@Overridepublic void eat(String something) {System.out.println("你吃:" + something);}
}class YouInvocation implements InvocationHandler {private Object targetObj;public YouInvocation() {}public YouInvocation(Object targetObj) {this.targetObj = targetObj;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("帮你泡火鸡面,带到你面前");// 调用原对象的方法Object result = method.invoke(targetObj, args);System.out.println("你说:谢谢宝~");return result;}
}// 代理工厂
class ProxyFactory {/*** 根据原对象生成代理对象** @param targetObj 原对象* @return 代理对象*/public static Object getProxyObject(Object targetObj){return Proxy.newProxyInstance(targetObj.getClass().getClassLoader(),targetObj.getClass().getInterfaces(), new YouInvocation(targetObj));}
}

运行结果:

你吃:火鸡面
========================
帮你泡火鸡面,带到你面前
你吃:火鸡面
你说:谢谢宝~

3、JDK动态代理的局限性

  • ①、只能代理实现了接口的类: JDK动态代理的机制要求被代理的类必须实现至少一个接口,因为它是基于接口来生成代理类的。这意味着如果目标类没有实现接口,就无法使用JDK动态代理。

  • ②、无法直接代理类的方法: JDK动态代理只能代理接口中定义的方法,无法直接代理类中的方法。如果需要代理类中的方法,就需要使用CGLIB等其他代理机制。

  • ③、性能相对较低: 由于JDK动态代理是基于Java反射机制实现的,相比较于静态代理或者其他代理方式,它的性能会相对较低一些。这是因为在运行时生成代理类和方法调用的过程中,需要进行额外的反射操作,会带来一定的性能开销。

4、使用CGLIB代理机制完成未实现接口的类的代理

CGLIB(Code Generation Library)是一个Java字节码生成库,它被广泛用于在运行时动态生成新的类以实现代理、混入(Mixin)和其他类似的功能。
CGLIB通过生成目标类的子类,并在子类中重写需要代理的方法来实现代理功能,因此它可以代理那些没有实现接口的类。
CGLIB通常与其他代理机制(如JDK动态代理)相比具有更高的性能,因为它不需要依赖于接口,而且可以代理类中的方法,但是不包括final方法,因为被final修饰的方法无法被子类重写。

Spring框架就集成了CGLIB,默认情况下Spring 的AOP功能实现代理的方式:如果目标对象实现了接口,默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。

CGLIB中实现动态代理的关键是 MethodInterceptor 接口和 Enhancer 类。
MethodInterceptor接口中的intercept方法 用来拦截增强被代理类的方法 ,Enhancer类的create方法用来创建代理对象。

CGLIB动态代理的实现步骤:

  • ①、引入CGLIB坐标, 目前最新版本为3.3.0
    https://central.sonatype.com/artifact/cglib/cglib/versions
    在这里插入图片描述
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>

选择目标类:选择需要代理的目标类,这个目标类可以是任意的普通类,不一定需要实现接口;

  • ②、实现 MethodInterceptor接口,重写 intercept 方法用于拦截增强被代理类的方法;
  • ③、创建Enhancer对象设置相应参数,调用 create()方法创建代理类;

CGLIB动态代理图示:
在这里插入图片描述

CGLIB动态代理代码示例:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class TestDynamicProxy {public static void main(String[] args) {You you = new You();Class<? extends You> youClass = you.getClass();// 创建动态代理增强类Enhancer enhancer = new Enhancer();// 设置类加载器enhancer.setClassLoader(youClass.getClassLoader());// 设置被代理类enhancer.setSuperclass(youClass);// 设置自定义的代理类拦截器enhancer.setCallback(new YourGirlFriend());// 创建代理类You youProxy = (You) enhancer.create();you.eat("火鸡面");System.out.println("================");youProxy.eat("火鸡面");}
}/*** 这个类代表你 (被代理类)*/
class You {public void eat(String something) {System.out.println("你吃:" + something);}public void sleep() {System.out.println("你睡觉了");}
}/*** 代理类 实现MethodInterceptor 重写 intercept*/
class YourGirlFriend implements MethodInterceptor {/*** @param o           被代理对象* @param method      被代理方法* @param objects     方法入参* @param methodProxy 用来调用原始方法* @return 方法返回值* @throws Throwable 异常*/@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {if(method.getName().equals("eat")) {System.out.println("帮你泡火鸡面,带到你面前");} else if(method.getName().equals("sleep")) {System.out.println("帮你盖好被子,给你讲个故事");}Object object = methodProxy.invokeSuper(o, objects);if(method.getName().equals("eat")) {System.out.println("你说:谢谢宝~");} else if(method.getName().equals("sleep")) {System.out.println("你说:晚安~");}return object;}
}

5、JDK动态代理和CGLIB动态代理对比

①、原理

  • JDK动态代理:
    JDK动态代理的原理主要基于Java的反射机制和java.lang.reflect.Proxy类与java.lang.reflect.InvocationHandler接口。
    当通过代理对象调用方法时,实际上是调用了InvocationHandler的invoke()方法。这个方法内部会通过反射调用实际被代理对象的对应方法,并可以在此前后添加额外的处理逻辑。
    在调用Proxy.newProxyInstance()时,如果代理类还没有被创建,JVM会动态地生成一个实现上述指定接口的代理类的字节码,并加载到JVM中。这个过程是透明的,开发者无需关心具体的生成细节。

  • CGLIB动态代理:
    CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用。
    CGLIB动态代理的核心在于通过继承和字节码操作技术,在运行时动态生成目标类的子类,并在子类中插入自定义的拦截逻辑,以此来达到在不修改目标类源码的情况下增强或控制其行为的目的。

②、使用场景

  • JDK动态代理:
    目标类实现了接口,简单的功能增强

  • CGLIB动态代理:
    无接口的类,复杂的AOP逻辑

③、性能(下面的说法有待考证)

  • JDK动态代理:
    在早期JDK版本中,JDK动态代理的性能通常被认为低于CGLIB,主要是因为每次方法调用都需要通过反射(Method.invoke())来完成,反射操作相对较慢。
    但从JDK 1.8开始,尤其是随着后续版本的不断优化,JDK动态代理的性能有了显著提升。特别是在某些场景下,其性能已经与CGLIB相当,甚至有所超越。
    JDK动态代理在创建代理对象时的开销较小,因为它基于接口实现,不需要生成大量的字节码。

  • CGLIB动态代理:
    CGLIB通过字节码技术生成目标类的子类,这种方式在创建代理实例时的开销较大,因为需要生成新的字节码。
    一旦代理对象创建完成,CGLIB的直接方法调用(通过子类覆盖父类方法)在运行时通常比JDK动态代理的反射调用更快,特别是在频繁调用方法的场景下(这个在JDK8版本的测试下,优势并不明显,也许是方法调用的次数还不够多)。
    CGLIB能够代理没有实现接口的类,提供了更广泛的适用范围,但这也意味着在运行时对类进行了修改,增加了潜在的风险。

简单测试下(使用StopWatch计算,JDK8版本):
分别使用JDK和CGLIB的动态代理去测试创建对象的速度,和调用代理方法的速度,分别进行创建50000次对象的时间比较和使用创建好的代理对象调用50000次代理方法的比较。
PS:被代理类是一样的,只是放在了不同的包下,其中JDK的被代理类多实现了一个接口。

测试代码如下:


import com.kinggm.testb.ProxyFactory;
import com.kinggm.testb.YourBehavior;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.springframework.util.StopWatch;import java.lang.reflect.Method;public class TestDynamicProxy {public static void main(String[] args) {StopWatch stopWatch = new StopWatch("测试CGLIB和JDK动态代理创建50000次代理对象的时间占比");stopWatch.start("CGLIB创建代理对象50000次");for (int i = 0; i < 50000; i++) {You you = new You();Class<? extends You> youClass = you.getClass();// 创建动态代理增强类Enhancer enhancer = new Enhancer();// 设置类加载器enhancer.setClassLoader(youClass.getClassLoader());// 设置被代理类enhancer.setSuperclass(youClass);// 设置自定义的代理类拦截器enhancer.setCallback(new YourGirlFriend());// 创建代理类You youProxy = (You) enhancer.create();}stopWatch.stop();stopWatch.start("JDK创建代理对象50000次");for (int i = 0; i < 50000; i++) {com.kinggm.testb.You you = new com.kinggm.testb.You();// 获取代理对象com.kinggm.testb.YourBehavior proxyInstance = (YourBehavior) ProxyFactory.getProxyObject(you);}stopWatch.stop();// ============================================================================================================You you = new You();Class<? extends You> youClass = you.getClass();// 创建动态代理增强类Enhancer enhancer = new Enhancer();// 设置类加载器enhancer.setClassLoader(youClass.getClassLoader());// 设置被代理类enhancer.setSuperclass(youClass);// 设置自定义的代理类拦截器enhancer.setCallback(new YourGirlFriend());// 创建代理类You youProxy = (You) enhancer.create();com.kinggm.testb.You you1 = new com.kinggm.testb.You();// 获取代理对象com.kinggm.testb.YourBehavior proxyInstance = (YourBehavior) ProxyFactory.getProxyObject(you1);StopWatch stopWatch1 = new StopWatch("测试CGLIB和JDK动态代理使用代理对象调用50000次代理方法的时间占比");stopWatch1.start("CGLIB代理调用方法50000次");for (int i = 0; i < 50000; i++) {youProxy.eat("火鸡面");}stopWatch1.stop();stopWatch1.start("JDK代理调用方法50000次");for (int i = 0; i < 50000; i++) {// 使用代理对象 调用方法proxyInstance.eat("火鸡面");}stopWatch1.stop();System.out.println(stopWatch.prettyPrint());System.out.println("========================================");System.out.println(stopWatch1.prettyPrint());}
}/*** 这个类代表你 (被代理类)*/
class You {public void eat(String something) {System.out.println("你吃:" + something);}public void sleep() {System.out.println("你睡觉了");}
}/*** 代理类 实现MethodInterceptor 重写 intercept*/
class YourGirlFriend implements MethodInterceptor {/*** @param o           被代理对象* @param method      被代理方法* @param objects     方法入参* @param methodProxy 用来调用原始方法* @return 方法返回值* @throws Throwable 异常*/@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("帮你泡火鸡面,带到你面前");Object object = methodProxy.invokeSuper(o, objects);System.out.println("你说:谢谢宝~");return object;}
}

运行三次结果如下:

第一次:
StopWatch '测试CGLIBJDK动态代理创建50000次代理对象的时间占比': running time = 250062000 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
183527300  073%  CGLIB创建代理对象50000066534700  027%  JDK创建代理对象50000========================================
StopWatch '测试CGLIBJDK动态代理使用代理对象调用50000次代理方法的时间占比': running time = 1168326000 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
583097900  050%  CGLIB代理调用方法50000585228100  050%  JDK代理调用方法50000次第二次:
StopWatch '测试CGLIBJDK动态代理创建50000次代理对象的时间占比': running time = 270423500 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
198542800  073%  CGLIB创建代理对象50000071880700  027%  JDK创建代理对象50000========================================
StopWatch '测试CGLIBJDK动态代理使用代理对象调用50000次代理方法的时间占比': running time = 1194475300 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
616564500  052%  CGLIB代理调用方法50000577910800  048%  JDK代理调用方法50000次第三次:
StopWatch '测试CGLIBJDK动态代理创建50000次代理对象的时间占比': running time = 243452600 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
184046700  076%  CGLIB创建代理对象50000059405900  024%  JDK创建代理对象50000========================================
StopWatch '测试CGLIBJDK动态代理使用代理对象调用50000次代理方法的时间占比': running time = 1227753400 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
572891200  047%  CGLIB代理调用方法50000654862200  053%  JDK代理调用方法50000

从结果可以看出基本符合上面的性能总结:
JDK的动态代理创建代理对象的速度比CGLIB快的多,代理对象调用方法的速度不相上下。

6、JDK动态代理为什么只能代理实现接口的类?

如果别人问我这个问题,我会这样回答:
因为JDK提供的创建代理对象的API

Proxy.newProxyInstance(targetObj.getClass().getClassLoader(),targetObj.getClass().getInterfaces(), new YouInvocation(targetObj));

第二个参数就是要传被代理对象实现的接口,这是王八的屁股,规定!

网上还有一种说法是:
因为Java语言的类是单继承的,JDK自动生成的代理类已经继承了Proxy类,要想保证代理类和被代理类有一致的行为(方法),显然不能通过继承重写方法来实现了,因为JDK自动生成的代理类已经继承了Proxy类;那么只好让被代理类再实现一个接口,JDK自动生成的代理类也去实现这个接口,就能实现代理类和被代理类有一致的行为(方法)了。那么在代理类中重写被代理类的方法就能完成代理(功能增强)。

**对于任何网上的说法持怀疑态度 **
有些比较模糊的观点最好还是去比较权威的书上查证 ,或者去看源码查证

下面就去看看JDK动态代理相关的源码,我们来考证下网上的说法对不对。

进入Proxy.newProxyInstance方法中
有个getProxyClass0方法 (注释:Look up or generate the designated proxy class —— 查找或者生成指定的代理类)
该方法返回生成的代理类的class对象
在这里插入图片描述
再往下看
在这里插入图片描述

再看下getProxyClass0方法内部:(英语真的挺重要,尤其是看源码注释的时候)
在这里插入图片描述
再继续看 ProxyClassFactory
ProxyClassFactory是个静态的内部工厂类
作用就是根据给定的类加载器和接口数组生成、定义并返回代理类
生成的代理类的前缀是$Proxy
在这里插入图片描述
再往下看 代理类包名和类名的生成规则
在这里插入图片描述
答案已经近在眼前了
进入ProxyGenerator.generateProxyClass方法
在这里插入图片描述

那我们就在JVM启动的时候 加上 启动参数 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

通过IDEA设置JVM启动参数:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这个时候再执行下代码 查看结果:

public static void main(String[] args) throws Exception{YourBehavior you = new You();Class<?> youClass = you.getClass();MyInvocationHandler myInvocationHandler = new MyInvocationHandler(you);YourBehavior proxyInstance = (YourBehavior)Proxy.newProxyInstance(youClass.getClassLoader(),youClass.getInterfaces(),myInvocationHandler);proxyInstance.eat("火鸡面");System.out.println(proxyInstance.getClass());}

结果:

帮你泡火鸡面,带到你面前
你吃:火鸡面
你说:谢谢宝~
class com.kinggm.test.$Proxy0

可以看到生成的代理类全限定名为: com.kinggm.test.$Proxy0

生成的文件在 项目根目录下:
在这里插入图片描述
由于我的接口

interface YourBehavior{void eat(String something);
}

并没有加public
所以生成的代理类包名 并不是默认的 com.sun.proxy 而是和我的接口包名相同
并且继承了 Proxy类 和被代理类实现了相同的接口YourBehavior
在这里插入图片描述
最后再看下 JDK自动生成的代理类结构:
(实际上生成的是.class文件,IDEA通过 FernFlower decompiler 插件把 .class字节码文件反编译成我们能看懂的.java类文件)

我们主要看三个地方$Proxy0的构造方法,初始化,重写的eat方法

①、构造方法

 public $Proxy0(InvocationHandler var1) throws  {super(var1);}

实际上执行的是父类 Proxy的构造方法,把我们自定义的InvocationHandler 传给父类Proxy中的InvocationHandler h

  protected InvocationHandler h;protected Proxy(InvocationHandler h) {Objects.requireNonNull(h);this.h = h;}

②、静态代码块初始化
其中m3 就是我们实现的接口中的目标方法eat

private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m2 = Class.forName("java.lang.Object").getMethod("toString");m3 = Class.forName("com.kinggm.YourBehavior").getMethod("eat", Class.forName("java.lang.String"));m0 = Class.forName("java.lang.Object").getMethod("hashCode");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}

③、重写的目标(eat)方法

public static void main(String[] args) {YourBehavior you = new You();MyInvocationHandler myInvocationHandler = new MyInvocationHandler(you);YourBehavior proxyInstance = (YourBehavior) Proxy.newProxyInstance(you.getClass().getClassLoader(),you.getClass().getInterfaces(),myInvocationHandler);proxyInstance.eat("火鸡面");}

当我们执行代理类的eat方法时实际上执行的是 $Proxy0中的 eat方法

public final void eat(String var1) throws  {try {super.h.invoke(this, m3, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}

$Proxy0中的 eat方法调用的是父类中初始化的 MyInvocationHandler 中的invoke方法

class MyInvocationHandler implements InvocationHandler {private YourBehavior targetObj;public MyInvocationHandler() {}public MyInvocationHandler(YourBehavior targetObj) {this.targetObj = targetObj;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("帮你泡火鸡面,带到你面前");Object result = method.invoke(targetObj,args);System.out.println("你说:谢谢宝~");return result;}
}

所以在执行目标方法eat前后,你女朋友帮你泡火鸡面,带到你面前,你对你女朋友说了 谢谢宝~

分析至此, 发现网上说的好像也有点道理, JDK自动生成的代理类利用继承Proxy来初始化InvocationHandler ,并处理代理对象的生成逻辑,Java又是单继承的语言,想保证代理类和被代理类有一致的行为(方法),就只好让被代理类再实现一个接口,JDK自动生成的代理类也去实现这个接口,就能实现代理类和被代理类有一致的行为(方法)了。最后在代理类中重写被代理类的方法通过调用父类Proxy中初始化的自定义InvocationHandler 中的invoke方法,就能执行到我们自己写的增强逻辑了,同时invoke方法中又去调用了原方法,就完成了整个代理过程喽。

最后再画个图总结下吧:
在这里插入图片描述

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

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

相关文章

vue3使用vue3-print-nb打印

打印效果 1.下载插件 Vue2.0版本安装方法 npm install vue-print-nb --saveVue3.0版本安装方法&#xff1a; npm install vue3-print-nb --save2.main.js引入 vue2引入 import Print from vue-print-nb Vue.use(Print)vue3引入 import print from vue3-print-nb // 打印…

实时监控电脑屏幕的软件是什么?三款超受欢迎的电脑监控软件

实时监控电脑屏幕的软件在现代企业管理中扮演着至关重要的角色&#xff0c;它们不仅帮助管理者实时监控员工的工作状态&#xff0c;提高工作效率&#xff0c;还通过数据分析和报告功能&#xff0c;为企业提供了优化管理流程和决策支持的依据。以下将介绍几款市面上广泛使用的实…

大模型部署_书生浦语大模型 _作业2基本demo

本节课可以让同学们实践 4 个主要内容&#xff0c;分别是&#xff1a; 1、部署 InternLM2-Chat-1.8B 模型进行智能对话 1.1安装依赖库&#xff1a; pip install huggingface-hub0.17.3 pip install transformers4.34 pip install psutil5.9.8 pip install accelerate0.24.1…

为新质生产力注入人才“活水”

21世纪最缺的是什么&#xff1f;这个梗到今天仍有现实意义&#xff0c;答案也依旧是那两个字——人才&#xff01;不过&#xff0c;随着数字化转型的深入&#xff0c;以及国家战略布局新质生产力&#xff0c;还是应该与时俱进&#xff0c;在这两个字的前面再加上一个定语&#…

Jlink驱动包

本文分享多个版本的Jlink安装驱动包。 链接: https://pan.baidu.com/s/19P2HymfPTFK2IEfAjEoSpA 提取码: cj6k 主要分享如下版本的&#xff1a; Jlink安装驱动方法&#xff1a; 点击下一步&#xff1a; 点击 同意 然后进行安装。 安装完成后点击完成 使用方法&#xff1a; …

【Linux】Linux工具——yum,vim

1.Linux 软件包管理器——yum Linux安装软件&#xff1a; 源代码安装&#xff08;不建议&#xff09;rpm安装&#xff08;类似Linux安装包&#xff0c;版本可能不兼容&#xff0c;不推荐&#xff0c;容易报错&#xff09;yum安装&#xff08;解决了安装源&#xff0c;安装版本&…

四川音盛佳云电子商务有限公司引领抖音电商新风潮

在数字化浪潮席卷全球的今天&#xff0c;电商行业已成为推动经济发展的重要力量。作为这一领域的佼佼者&#xff0c;四川音盛佳云电子商务有限公司凭借其在抖音电商服务领域的专业实力和独特视角&#xff0c;正引领着行业的新风潮&#xff0c;助力品牌实现快速增长和腾飞。 四…

使用Python发送企业微信消息

大家好&#xff0c;在本文中&#xff0c;我们将探讨如何使用 Python 发送企业微信消息。将详细说明如何通过 Python 脚本实现消息的发送。无论是希望自动化某些任务&#xff0c;还是想要快速地向团队发送实时通知&#xff0c;本文都将为您提供一站式的解决方案。 企业微信提供了…

找不到msvcr100.dll如何修复,分享几种有效的修复方法

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“找不到msvcr100.dll”。这个错误通常发生在运行某些程序时&#xff0c;系统无法找到所需的动态链接库文件。这个问题可能会给用户带来困扰&#xff0c;但是幸运的是&#xff0c;有一些简单…

汇编原理 | 二进制、跳转指令、算数运算、

一.二进制 two complement reprentation&#xff08;补码&#xff09; 二进制的运算&#xff1a; 6的二进制 0110 -6的二进制 如何表示&#xff1f; 四个bit的第一个bit表示符号&#xff1a;1负0正 -6表示为1010 解释&#xff1a; 0 0000 1 0001 -1 1111&#xff08;由 …

【Ubuntu】100 系统字体安装和更改

系统&#xff1a;Ubuntu18.04LTS 1 Why we need&#xff1f; 写这篇经验贴的原因&#xff1a; ①我需要装一下中文字体&#xff08;Qt要用&#xff09;&#xff1b; ②想调一下字体大小和默认中文字体的样式 2 装第三方字体 Step1&#xff1a;安装软件Font Manager sudo ap…

【记录】打印|用浏览器生成证件照打印PDF,打印在任意尺寸的纸上(简单无损!)

以前我打印证件照的时候&#xff0c;我总是在网上找在线证件照转换或者别的什么。但是我今天突然就琢磨了一下&#xff0c;用 PDF 打印应该也可以直接打印出来&#xff0c;然后就琢磨出来了&#xff0c;这么一条路大家可以参考一下。我觉得比在线转换成一张 a4 纸要方便的多&am…

Echarts 让柱状图在图表中展示,离开X轴

文章目录 需求分析需求 分析 话不多说,直接源码展示 option = {title: {text: Waterfall Chart,subtext: Li

落地台灯有什么作用?五款口碑好的落地台灯推荐

落地台灯有什么作用&#xff1f;面对长时间工作、学习已成为当代年轻人的真实写照&#xff0c;据目前不完全统计&#xff0c;60%以上的人群每天用眼时间都已经超过10小时&#xff0c;高强度的的用眼以及不可确定的环境因素都易导致双眼出现干涉、酸痛、红血丝等情况&#xff0c…

[有监督学习]6.详细图解朴素贝叶斯

朴素贝叶斯 朴素贝叶斯&#xff08;Naive Bayes&#xff09;是常用于自然语言分类问题的算法。它在垃圾邮件过滤上的应用非常有名。 概述 朴素贝叶斯是一个基于概率进行预测的算法&#xff0c;在实践中被用于分类问题。具体来说&#xff0c;就是计算数据为某个标签的概率&…

从零开始利用MATLAB进行FPGA设计(七)用ADC采集信号教程2

黑金的教程做的实在太拉闸了&#xff0c;于是自己摸索信号采集模块的使用方法。 ADC模块&#xff1a;AN9238 FPGA开发板&#xff1a;AX7020&#xff1b;Xilinx 公司的 Zynq7000 系列的芯片XC7Z020-2CLG400I&#xff0c;400引脚 FBGA 封装。 往期回顾&#xff1a; 从零开始利…

STM32学习问题总结(2)—CubeMX生成项目后串口没效果和Microlib

检查完所有的硬件和软件部分&#xff0c;最后发现&#xff0c;又是Keil的设置问题&#xff0c;啊啊啊啊 打开Keil的魔术棒&#xff0c;勾选Target的Use Microlib选项即可&#xff0c;但这并不是最佳方案 最终解决方案&#xff1a; 参考&#xff1a;http://t.csdnimg.cn/2Tjfc…

服务器主板电池

一、什么是服务器纽扣电池&#xff1f; 服务器纽扣电池&#xff0c;也叫CMOS电池&#xff0c;是一种非常小型的电池&#xff0c;通常与服务器主板上的CMOS芯片相结合&#xff0c;用于储存BIOS设置、时钟和其他关键系统信息。这种电池的体积通常比一枚硬币还小&#xff0c;而且…

四、.Net8对接Ollama实现文字翻译(.Net8+SemanticKernel+Ollama)本地运行自己的大模型

.Net8SemanticKernelOllama 一、Semantic Kernel官方定义SK能做什么&#xff1f; 二、基本使用1、普通对话2、使用插件实现文本翻译功能 三、IChatCompletionService、ITextGenerationService、ITextEmbeddingGenerationService 很多情况都有这样的需求&#xff0c;使用自有系统…

巨细巨细的白痴级vulntarget-a靶场wp再不会你打死我

ad一&#xff0c;靶场搭建 下载靶场&#xff1a;GitHub - crow821/vulntarget: vulntarget靶场系列 官方拓补图 ps&#xff1a;此处 攻击机ip192.168.87.134&#xff0c;win7ip1为192.168.87.144 下载完毕后直接装入虚拟机不要进去&#xff0c;不要进去&#xff0c;不要进去…