代理模式:JDK动态代理和静态代理回顾

news/2024/5/19 1:04:30/文章来源:https://blog.csdn.net/w15558056319/article/details/129686358

背景:Spring主要有两大思想:IoC、AOP。对于IoC依赖注入不多说了,对于Spring的核心AOP来说,我们需要了解其底层的实现原理:java的动态代理机制。

 

本篇随笔就是对java的动态机制进行一个回顾。

代理模式的理解

类型:代理模式是GoF23种设计模式之一。属于结构型设计模式。

特点:对于客户端程序来说,使用代理对象时就像在使用目标对象一样。

意义:使用代理模式可以在不修改别代理对象代码的基础上,通过扩展代理类,进行一些功能的附加与增强

生活场景:

拍电影时,演员找替身演员,这就是一个代理模式。由替身演员——》去代理演员——》完成表演

思考:演员为什么要找替身呢?——为什么要使用代理模式呢?
    原因 ①:怕自己受伤,找个替身。(保护自己)
    原因 ②:自己完成不来这种高难度的动作,替身演员可以完成。(功能增强)

在java程序中的代理模式的作用:

  • 当一个对象需要 受到保护 时,可以考虑使用代理对象去完成某个行为
  • 需要给某个对象的功能进行 功能增强 的时候,可以考虑找一个代理进行增强。
  • A对象和B对象 无法直接交互 时,也可以使用代理模式来解决。(我是律师,我的当事人不方便和你对话。)

业务场景:

系统中有A,B,C三个模块,使用这些模块的前提是需要用户登陆,这样会导致三个模块都要写一套一样的登陆代码,可以为A,B,C三个模块提供一个代理,在代理当中写一次登陆判断即可。

代理的逻辑是:请求来了之后,判断用户是否登陆了,如果已经登陆了,则执行对应的目标,如果没有登陆则跳转到登陆页面【在程序中,目标不但受到保护,并且代码也得到了复用】

代理模式中的角色:

  • 代理类(代理主题)
  • 目标类(真实主题)
  • 代理类和目标类的公共接口(抽象主题):客户端在使用代理类时就像在使用目标类,不被客户端所察觉,所以代理类和目标类要用共同的行为,也就是实现共同的接口。

简单理解:目标对象是演员,代理对象是替身演员,所谓的公共接口就是演员和替身演员应该具有相同的行为动作,  不让观众察觉到替身演员。这里的观众其实就是“客户端程序”。


 

代理模式的类图:

代理模式类型

代理模式在代码实现上,包括两种形式

  • 静态代理:在程序运行前就存在代理类的字节码文件,代理类和委托类的关系在运行前确定
  • 动态代理 :在程序运行时,通过反射获取被代理类的字节码内容用来创建代理类

代理模式场景

假设存在如下订单业务接口

1. OrderService 订单接口代码如下

// 订单接口
public interface OrderService {// 创建订单void create();// 修改订单void update();// 查询订单void detail();
}

2. 订单接口的实现类: OrderServiceImpl 

// 订单接口的实现类
public class OrderServiceImpl implements OrderService{// 创建订单@Overridepublic void create() {// 模拟业务调用时长消耗try {Thread.sleep(1125);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("创建订单成功。");}// 修改订单@Overridepublic void update() {// 模拟业务调用时长消耗try {Thread.sleep(984);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("修改订单成功。");}// 查询订单@Overridepublic void detail() {// 模拟业务调用时长消耗try {Thread.sleep(111);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("查询订单成功。");}
}

3. 测试类如下

public class Test {public static void main(String[] args) {OrderService order = new OrderServiceImpl();order.create();order.update();order.detail();}
}

问题思考:这个功能已经运行了一年多了,突然客户提出一个新需求:要统计所有业务接口中每一个业务方法的耗时。你会怎么处理?

解决方案一:硬编码

在每一个接口方法里都加入计算时长的代码

 1.如下图所示,在OrderServiceImpl  里面的每个业务方法都加入耗时统计

public class OrderServiceImpl implements OrderService{@Overridepublic void create() {// ------ 加入统计计算long begin = System.currentTimeMillis();// 模拟业务调用时长消耗try {Thread.sleep(1125);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("创建订单成功。");// ------ 加入统计计算long end = System.currentTimeMillis();System.out.println("耗时:"+(end-begin));}....}

2. 运行结果如下:

➳ 说明:这种方案虽然能够解决业务需求,但是存在两个缺点, 第一 违背OCP开闭原则  第二代码没有得到复用(相同的代码写了很多遍)

解决方案二:扩展业务类的子类

编写OrderServiceImpl 的子类,让子类继承业务类,对每个业务方法进行重写

1. 如下图所示,OrderServiceImpl  保持与原来不变,新增其子类 OrderServiceImplSub  

public class OrderServiceImplSub extends OrderServiceImpl{@Overridepublic void create() {long begin = System.currentTimeMillis();// 调用父类OrderServiceImpl的方法super.create();long end = System.currentTimeMillis();System.out.println("耗时:"+(end-begin));}@Overridepublic void update() {long begin = System.currentTimeMillis();super.update();long end = System.currentTimeMillis();System.out.println("耗时:"+(end-begin));}@Overridepublic void detail() {long begin = System.currentTimeMillis();super.detail();long end = System.currentTimeMillis();System.out.println("耗时:"+(end-begin));}
}

 2. 测试类如下:

public class Test {public static void main(String[] args) {OrderService order = new OrderServiceImplSub();order.create();order.update();order.detail();}
}

 ➳ 说明:1.虽然解决了OCP开闭原则,但这种方式会导致耦合度很高,因为采用了继承关系,继承关系时一种耦合度非常高的关系,不建议使用  2.代码同样没有得到复用

解决方案三:静态代理模式

编写OrderServiceImpl 的代理类,增强其业务方法,最后调用代理类的代理目标方法

静态代理模式的基本操作逻辑如下:

  • ① 明确目标对象即被代理的对象;
  • ② 定义代理对象,通过构造器持有目标对象;
  • ③ 代理对象中定义前后置增强方法;

1. 新建 OrderServiceProxy  代理类,注意代理类和目标类的行为特征是一样的,所以一定也是实现了OrderService接口

/*** @Author wpf* @Date 2023/3/21 15:20* @Desc 代理类* ① 明确目标对象即被代理的对象:代理对象和目标对象要具有相同的行为,要实现同一个或同一些接口*/
public class OrderServiceProxy implements OrderService{ /**将目标对象作为代理对象的一个属性。这种关系叫做关联关系,比继承关系的耦合度低代理对象中含有目标对象的引用,关联关系。has a*/// ② 定义代理对象,通过构造器持有目标对象;private OrderService target; // 创建代理对象的时候,传一个目标对象给代理对象public OrderServiceProxy(OrderService target) {this.target = target;}@Overridepublic void create() { // 代理方法// ③ 定义前后置增强方法long begin = System.currentTimeMillis();// 调用目标对象的目标方法target.create();// ③ 定义前后置增强方法long end = System.currentTimeMillis();System.out.println("耗时:"+(end-begin));}@Overridepublic void update() { // 代理方法// ③ 定义前后置增强方法long begin = System.currentTimeMillis();// 调用目标对象的目标方法target.update();// ③ 定义前后置增强方法long end = System.currentTimeMillis();System.out.println("耗时:"+(end-begin));}@Overridepublic void detail() { // 代理方法// ③ 定义前后置增强方法long begin = System.currentTimeMillis();// 调用目标对象的目标方法target.detail();// ③ 定义前后置增强方法long end = System.currentTimeMillis();System.out.println("耗时:"+(end-begin));}}

2. 测试类及执行结果如下

public class Test {public static void main(String[] args) {// 创建目标对象OrderService target = new OrderServiceImpl();// 创建代理对象OrderServiceProxy proxy = new OrderServiceProxy(target);// 调用代理对象的代理方法proxy.create();proxy.update();proxy.detail();}
}

➳ 说明:1.解决了OCP开闭原则问题  2.采用代理模式的has a,可以降低耦合度。但静态代理这种方式也有一个很明显的缺点:  类爆炸! 假设系统中100个接口,那岂不是要写100个代理类?

可能有些人就问了,静态代理模式跟扩展子类也差不多啊,还是有这么多重复的代码,只不过一个是继承(方案二:扩展Impl的子类),一个是实现公共接口(方案三:静态代理)

错错错!方案二是泛化关系 is a ,例: cat is animal,方案三是关联关系 has a,例: I has a apple ,相比来说泛化关系的耦合度高于关联关系,所以优先使用关联关系!

注意:类之间包括6种关系。关系的强弱顺序为:泛化=实现 > 组合> 聚合> 关联> 依赖

静态代理解析

静态代理明确定义了代理对象,即有一个代理对象的.java文件加载到JVM的过程,很显然的一个问题,在实际的开发过程中,不可能为每个目标对象都定义一个代理类,同样也不能让一个代理对象去代理多个目标对象,这两种方式的维护成本都极高。

☁ 思考:怎么解决类爆炸问题?
 

解决方案四:动态代理模式(JDK)

在内存中动态的生成字节码代理类的技术,叫做:动态代理

在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量,解决代码复用的问题。

这里需要了解一个5毛钱的小知识点

硬盘中的class文件和内存这种的class文件区别不大,唯一区别是硬盘上的class文件,关机之后文件还在,内存中的class是关机就没有了的。
相同点:硬盘上的class文件要想执行,得先通过类加载器加载到内存中才可以执行,同样内存里生成的class也是一样的,必须先通过类加载器加载到内存中

场景解释

基于一个场景来描述动态代理和静态代理的区别,即最近几年很火的概念,海外代购:

在代购刚兴起的初期,是一些常去海外出差的人,会接代购需求,即代理人固定;后来就兴起海外代购平台,海淘等一系列产品,即用户代购需求(目标对象)由代购平台去实现,但是具体谁来操作这个就看即时分配,这个场景与动态代理的原理类似。

代码实操

这里先贴具体代码,详细参数解析滑至下方JDK动态代理技术

1. 由于是动态生成class代理类,因此直接写一个测试类即可,也就是模拟客户端

public class JdkProxyTest {public static void main(String[] args) {// 创建目标对象OrderService target = new OrderServiceImpl();/**newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)1.ClassLoader loader:(目标类)类加载器2.Class<?>[] interfaces:(目标类)实现接口,从面向对象来考虑,接口与实现分离,代理类通过实现xx接口,模拟目标类的需求3.InvocationHandler h:代理类提供的功能封装,可以在目标方法调用前后做增强处理注意点:类加载器必须和目标类的加载器是同一个,所以这里直接传入target.getClass().getClassLoader()实现接口也是一样,代理对象和目标对象要具有相同的行为,就要实现同一个或同一些接口*/// 创建代理类,注意类型转换:代理对象和目标对象实现的接口一样,所以可向下转型OrderService proxy = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimerInvocationHandler(target));// 调用代理类的目标方法,在这一步TimerInvocationHandler的invoke方法被调用proxy.create();//proxy.detail();//proxy.update();}
}

2. 实现 InvocationHandler 接口,写具体增强代码逻辑   

/*** @Author wpf* @Date 2023/3/22 11:23* @Desc 专门负责计时的一个调用处理器对象* 在当前调用处理器中编写计时相关的增强代码,该调用处理器只需写一次————这个接口的目的地就是为了让你有地方写增强代码的*/
public class TimerInvocationHandler implements InvocationHandler {// 目标对象private Object target;// 构造函数接收一个目标对象,主要用于invoke方法中的使用public TimerInvocationHandler(Object target){// 赋值成员变量this.target = target;}/**public Object invoke(Object proxy, Method method, Object[] args)1.Object proxy:代理对象的引用,很少使用(代理后的实例对象com.sun.proxy.$Proxy0)2.Method method:目标对象的目标方法3.Object[] args:目标方法上的实参*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("(这里写增强代码)执行invoke...");// 注意:调用代理对象到的代理方法时,如果要做增强对的话,目标对象的目标方法需要执行return method.invoke(target,args);}
}

3. 执行结果如下

如上图所示,在调用目标方法create()时,invoke方法就会被调用,在invoke里我们可以编写具体增强的代码逻辑,比如统计该方法的耗时等等

 

»» 头脑风暴

1. 为什么强行要求你必须实现InvocationHandler接口?
     a. 因为一个类实现接口就必须实现接口中的方法。
     b. JDK在底层调用invoke()方法的程序已经提前写好了

注意:invoke()方法不是我们程序员负责调用的,是JDK负责调用的!

2.invoke方法什么时候被调用呢?
  当代理对象调用代理方法时,注册在InvocationHandler当中的invoke()方法被调用


 

动态代理技术类型 

在内存当中动态生成类的技术常见的包括:

  1. JDK动态代理技术:只代理接口
  2. CGLIB动态代理技术:CGLIB(Code Generation Library) 是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理好(底层有一个小而快的字节码处理框架ASM)
  3. Javassist动态代理技术:Javassist是一个开源的分析,编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的千叶滋所创建的。它已加入了开放源代码JBoss应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态“AOP”框架

JDK动态代理技术

在JDK动态代理中,要求代理类的类加载器必须得和目标类的加载器一致,也就是必须和目标类的加载器是同一个!

首先看两个核心类,这里简述下概念,看完基本过程再细聊:

  • ① Proxy-创建代理对象,核心参数:

    • ClassLoader:(目标类的)加载器
    • Interfaces:(目标类的)接口数组
    • InvocationHandler:代理调用机制
  • ② InvocationHandler-代理类调用机制:

    • Object proxy:代理对象的引用(代理后的实例对象com.sun.proxy.$Proxy0)
    • Method method:目标对象的目标方法
    • Object[] :目标方法上的实参

Proxy语法:

Proxy.newProxyInstance(类加载器,代理类要实现的接口,调用处理器)

作用: 新建代理对象,通过调用该方法可以创建代理对象 

本质上Proxy.newProxyInstance方法的执行,主要做了两件事:

  1. 在内存中动态的生成了一个代理类的字节码class
  2. new对象了,通过内存中生成的代理类这个代码,实例化了代理对象

参数说明:

  •  ClassLoader loader:类加载器,加载类需要类加载器,所以要指定类加载器,并且JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个
  •  Class<?>[] interfaces:代理类和目标类要实现同一个接口或同一些接口,在内存中生成代理类的时候,这个代理类需要你告诉它实现哪些接口
  •  InvocationHandler h:调用处理器,是一个接口,在该接口中编写增强代码,因为具体要增强什么代码,JDK动态代理技术它是猜不到的!(既然是接口,就要写接口的实现类)
     

 ▎InvocationHandler语法:

public Object invoke(目标对象的引用, 目标方法, 目标方法的实参)

作用: 通过动态代理对象调用一个方法时,该方法的调用会被转发到实现InvocationHandler接口类的invoke方法来调用 

参数说明:

  •  Object proxy:代理对象的引用,很少使用(代理后的实例对象com.sun.proxy.$Proxy0)
  •  Method method:目标对象的目标方法
  •  Object[] args:目标方法上的实参

invoke方法是JDK负责调用的,因此JDK调用这个方法时会自动传递这三个参数过来,我们可以直接拿来使用!

扩展知识点

方法调用四要素::哪个对象、哪个方法、传什么参数、返回什么值!

从上述动态代理代码实操处,InvocationHandler的实现类中invoke方法实际代码(如下),我们是通过传入了一个目标对象去调用方法的 method.invoke( target, args);   我开始写的时候没注意,直接把方法形参proxy给传进去了method.invoke( proxy , args); 导致运行的时候死循环调用报错!

原因:这个很好理解,proxy是代理类的对象,当该对象方法被调用时会触发InvocationHandler,而我们的InvocationHandler里又调用了proxy里的对象??递归循环调用自己!另外proxy对应的方法是没有实现的。最终导致结果就是循环的不停报错!

调用method.invoke( proxy , args); 死循环报错截图如下:

 

思考:JDK动态代理到底是怎么实现的呢?

JDK原生提供的动态代理就是通过反射实现的,但动态代理的实现方式还可以是ASM(一个短小精悍的字节码操作框架)、cglib(基于ASM)等,并不局限于反射。

我们来看看源码其中关键的地方,在newProxyInstance()发放中有这样几段:

思考:JDK动态代理为什么不能代理类,只能代理接口?

源码面前,了无秘密

 我们可以先从源码上分析,在测试代码中设置代理类的字节码文件可见,如下图所示

// 将JDK动态代理生成的类保存为.class文件(jdk1.8之前的写法)
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");// jdk1.8之后的写法
System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

打开字节码文件查看,代理类本质上是继承了Proxy类,Proxy的作用简单来说就是保存自定义的InvocationHandler,即用户自定义代理逻辑的增强代码,便于在方法代理时执行自定义InvocationHandler的逻辑($Proxy0具体源码请滑至下方)

解答说明

 Because:从源码头部可以看出,$Proxy0继承了类Proxy,在Java中由于是单继承,多实现机制,因此不能再extends继承类了,故只能implements实现接口。

 But:个人认为代理接口不是从源码上理解的因为单继承的问题而做出的让步,而恰恰是代理接口比代理类更好更灵活些,在 mybatis 和 jdbc等spi机制下,全部都是调接口,因为这样可以灵活的代理多个类。我相信,代理类绝对可以做到不去继承 Proxy,但是人家并没有这么做。

代理类源码


public final class $Proxy0 extends Proxy implements OrderService {private static Method m1;private static Method m2;private static Method m3;private static Method m5;private static Method m0;private static Method m4;// 通过反射获取到目标类的几个方法,基本的equals、toString、hashCode// 以及我们目标类中自己的方法,create、detail、updatestatic {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("jdkProxy.OrderService").getMethod("detail");m5 = Class.forName("jdkProxy.OrderService").getMethod("create");m0 = Class.forName("java.lang.Object").getMethod("hashCode");m4 = Class.forName("jdkProxy.OrderService").getMethod("update");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}}public $Proxy0(InvocationHandler var1) throws  {super(var1);}public final boolean equals(Object var1) throws  {try {return (Boolean)super.h.invoke(this, m1, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final String toString() throws  {try {return (String)super.h.invoke(this, m2, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}public final int hashCode() throws  {try {return (Integer)super.h.invoke(this, m0, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}// 这是我们目标类中的自己设置的方法public final void detail() throws  {try {super.h.invoke(this, m3, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}// 这是我们目标类中的自己设置的方法public final void create() throws  {try {super.h.invoke(this, m5, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}// 这是我们目标类中的自己设置的方法public final void update() throws  {try {super.h.invoke(this, m4, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}
}

CGLIB动态代理

既可以代理接口,又可以代理类,底层采用继承的方式实现,所以被代理的目标类不能使用final修饰

1. 创建UserService目标类

/*** @Desc 目标类* 正常来说是一个接口,再写子类或者实现类去写具体的逻辑,cglib会自动生成子类,不用自己写*/
public class UserService {public boolean login(String name,String password){System.out.println("正在校验身份....");if("admin".equals(name) && "123".equals(password)){return true;}return false;}public void loginOut(){System.out.println("正在退出系统...");}
}

2. 创建cglib的回调类,即具体增强代码的类

/*** @Author wpf* @Date 2023/4/4 17:15* @Desc 跟JDK一样,只不过JDK使用的是InvocationHandler,CGLIB用的是MethodInterceptor*/
public class TimerMethodInterceptor implements MethodInterceptor {/*** target:cglib生成的代理对象,表示增强的对象* method:被代理对象方法,即要拦截的方法* args:被拦截方法的参数* methodProxy: 代理方法*/   @Overridepublic Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// 前置增强long begin = System.currentTimeMillis();// 调用目标方法Object obj = methodProxy.invokeSuper(target, args);// 后置增强long end = System.currentTimeMillis();System.out.println("耗时:"+(end-begin)+"毫秒");return obj;}
}

3. 创建测试类

public class cglibTest {public static void main(String[] args) {// 1.创建字节码增强器对象// 该对象是CGLIB库中的核心对象,就是依靠它来生成代理类的Enhancer enhancer = new Enhancer();// 2.告诉cglib父类是谁?即目标类是哪个(cglib底层采用继承,生成目标类的子类方式)enhancer.setSuperclass(UserService.class);// 3.设置回调(等同于JDK动态代理中的调用处理器InvocationHandler)// 在CGLIB中不是InvocationHandler,而是方法拦截器接口:MethodInterceptorenhancer.setCallback(new TimerMethodInterceptor());// 4.创建代理对象。// 这一步做两件事://    1.在内存中生成UserService类的子类,即代理类的字节码//    2.创建代理对象// 父类是UserService,子类这个代理类一定是UserServiceUserService userService = (UserService) enhancer.create();System.out.println("使用cglib生成的代理类:"+userService);// 5.调用目标方法boolean success = userService.login("admin", "123");System.out.println(success?"登陆成功!":"登陆失败!");userService.loginOut();}
}

4. 执行结果如下:

 

 


 

CGLIB和JDK动态代理区别

主要区别:cglib可以代理接口和类,jdk只能代理接口

JDK 动态代理:

  • 只能对实现了接口的类生成代理,而不能针对类。
  • 利用拦截器(实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

因为采用的是继承,所以该类或方法最好不要声明成final,对于final类或方法是无法继承的


CGLIB 动态代理:

  • 对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法实现增强,底层采用继承方式
  • 利用 ASM 开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

注意点:Spring AOP不支持代理类内部方法调用的拦截,比如类中a方法调用b方法,切面拦截b方法会失败的


何时使用 JDK 还是 CGLIB 动态代理呢?

目标对象实现了接口,默认情况下会采用 JDK 的动态代理实现 AOP,也可以强制使用 CGLIB ;
如目标对象未实现接口,必须采用 CGLIB,Spring 会自动在 JDK 动态代理和 CGLIB 之间转换。

如何强制使用 CGLIB 实现 AOP?

  xml配置

<aop:aspectj-autoproxy proxy-target-class="true"/>

  注解配置

@EnableAspectJAutoProxy(proxyTargetClass = true)

JDK 和 CGLIB 哪个速度更快?

每一次 JDK 版本升级,JDK 代理效率都得到提升,而 CGLIB 代理消息确有点跟不上步伐。

jdk6 之前比使用 Java 反射效率要高。 JDK 6 之前比使用 Java 反射效率要高。

jdk6后逐步对 JDK 动态代理优化之后,在调用次数较少的情况下:JDK > CGLIB ,进行大量调用的时候,jdk6 和 jdk7 比 CGLIB 代理效率低一点,

jdk8 时,JDK 代理效率高于 CGLIB 代理。

结论

JDK 动态代理不需要第三方库支持,只需要 JDK 环境就可以进行代理,使用条件:

  • 实现InvocationHandler
  • 使用Proxy.newProxyInstance产生代理对象
  • 被代理的对象必须要实现接口

CGLIB 必须依赖于 CGLIB 的类库,其为需要被代理的类生成一个子类,覆盖其中的方法,实际上是一种继承。

总结:动态代理的使用场景

好处就是比较灵活,可以在运行的时候才切入改变类的方法,而不需要预先定义它。

动态代理一般我们比较少去手写,但我们用得其实非常多。在Spring项目中用的注解,例如依赖注入的@Bean、@Autowired,事务注解@Transactional等都有用到,换言之就是Srping的AOP(切面编程)。

这种场景的使用是动态代理最佳的落地点,可以非常灵活地在某个类,某个方法,某个代码点上切入我们想要的内容,就是动态代理其中的内容。

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

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

相关文章

三甲医院手术麻醉系统源码, C# .net 桌面软件 C/S版源码

手术麻醉管理系统源码&#xff0c;手麻系统源码&#xff0c;大型医院手术麻醉系统源码 相关技术&#xff1a;C#语言 前端框架&#xff1a;Winform 后端框架&#xff1a;WCF 数 据库&#xff1a;sqlserver VS2019 文末获取联系 手术麻醉系统是面向医生实际工作情况开发的专…

Excel宏(VBA)密码破解

最近在研究一个Excel宏&#xff0c;想查看VBA代码但是有密码&#xff0c;于是想着能不能移除密码。网上查找一番资料后进行了尝试。 一&#xff0c;准备工具 ExcelHex Editor Neo 二&#xff0c;开始实践 首先将.xlsm后缀名的文件改为.zip文件 然后双击zip文件(不用解压文件…

字节跳动CVPR 2023论文精选来啦(内含一批图像生成新研究)

计算机视觉领域三大顶会之一的 CVPR 今年已经开奖啦。 今年的 CVPR 将于六月在加拿大温哥华举办&#xff0c;和往年一样&#xff0c;字节跳动技术团队的同学们收获了不少中选论文&#xff0c;覆盖文本生成图像、语义分割、目标检测、自监督学习等多个领域&#xff0c;其中不少…

AUTOSAR SecOC的CAN FD应用

20多年来&#xff0c;CAN一直是并且仍然是车辆中的主导通信系统。 随着车载功能日益复杂&#xff0c;传统CAN已无法满足对有效数据速率日益增长的需求。 因此&#xff0c;引入了CAN FD—它允许高达64字节的有效载荷以实现2 Mbit/s 和5 Mbit/s的数据速率。为了将这一主要优势用于…

【CocosCreator入门】CocosCreator组件 | Mask(遮罩)组件

Cocos Creator 是一款流行的游戏开发引擎&#xff0c;具有丰富的组件和工具&#xff0c;其中Mask组件可用于创建如圆形、矩形和任意形状的遮罩效果&#xff0c;以限制节点显示的范围。这对于创建具有复杂布局的UI元素非常有用&#xff0c;例如只显示图片的一部分或控制文本显示…

C++相关面试题总结一——内存、关键字、STL、指针、排序、Lambda

面试题总结 基础 C是在C语言的基础上开发的一种面向对象编程语言&#xff0c;应用广泛。C支持多种编程范式&#xff1a;面向对象编程、泛型编程和过程化编程。其编程领域众广&#xff0c;常用于系统开发&#xff0c;引擎开发等应用领域&#xff0c;是最受广大程序员受用的最强…

JavaSE——方法的使用

目录 一、方法的概念及使用 1、什么是方法(method) 2、方法定义 3、方法调用的执行过程 4、实参和形参的关系 二、方法重载 1、为什么需要方法重载 2、方法重载概念 3、方法签名 三、递归 1、递归的概念 2、递归执行过程分析 3、递归练习 一、方法的概念及使用 1、…

【进阶C语言】内存函数(详解)

前言 上一期讲的函数都是和字符串相关的&#xff0c;但是我们在操作数据的时候&#xff0c;不仅仅是操作字符串的数据&#xff0c;还得需要内存函数的应用 内存函数的应用1. memcpy1.1 memcpy的介绍1.2 memcpy的使用1.3 模拟实现memcpy库函数1.4 我想在1&#xff0c;2后面打印1…

web学习---Vue---笔记(1)

该笔记是记录尚硅谷的Vue学习视频的笔记&#xff0c;视频地址为&#xff1a;学习视频地址 初始Vue Vue组件化的特点 组件化声明式编码虚拟DOMDiff算法&#xff0c;尽量复用DOM节点 H5的组件&#xff0c;是把某一个模块封装&#xff0c;里面写HTML\CSS\JS等&#xff0c;算是一…

关于软件发布等一系列注意事项

我们以VS for Qt 开发为案例 1、软件图标的使用&#xff1a; this->setWindowIcon(QIcon("写入路径"));注意这里的路径&#xff0c;一般需要你先添加图片到资源文件中 那么如何将图片添加到资源文件中呢&#xff1f; 1、打开qrc文件 2、添加前缀&#xff0c;添…

【Linux】八、Linux进程信号详解(一)

目录 一、认识信号 1.1 生活中的信号 1.2 将1.1的概念迁移到进程 1.3 信号概念 1.4 查看系统定义信号列表 1.5 man 7 signal 1.6 解释1.2的代码样例 1.7 信号处理常见方式概览 二、产生信号 2.1 signal函数 2.2 通过终端按键产生信号 2.3 调用系统函数向进程发信号…

Java小课堂:自定义注解(案例:自定义DecimalFormat注解)

文章目录 引言I 预备知识1.1 元注解1.2 Target注解的ElementType枚举1.3 Retention注解的RetentionPolicy枚举II 自定义注解2.1 基本条件2.2 注解自定义属性的格式III 案例3.1 自定义DecimalFormat注解3.2 自定义json序列化解析引言 需求: 编辑费率限制的值时填写几位就保存几…

动力节点王鹤SpringBoot3学习笔记——第五章 说说Web服务

目录 第五章 说说Web服务 5.1 高效构建Web应用 5.1.1 html页面视图 5.1.2 JSON视图 5.1.3 给项目加favicon 5.2 Spring MVC 5.2.1 控制器Controller 5.2.1.1 匹配请求路径到控制器方法 5.2.1.2 RequestMapping 5.2.1.3 控制器方法参数类型与可用返回值类型 5…

【从零开始学习 UVM】11.5、UVM Register Layer —— 后门访问 实战项目(RAL实战,交通灯为例)

文章目录 后门访问是什么?定义后门 HDL 路径示例sequence中的后门访问示例UVM寄存器模型允许使用前门访问DUT寄存器,就像我们之前在寄存器环境中看到的那样。 这意味着环境中的所有寄存器读写操作都会转换为总线事务,并驱动到设计的总线接口,就像典型系统中的任何其他硬件…

大数乘法【极简思路、代码模板】

793. 高精度乘法 - AcWing题库 极简思路 大数乘法可能和前面我们提到的大数减法 和 大数加法 的分治 思路不太一样&#xff0c;对于大数乘法我们可以有更加简单的思路。 这里提到的大数乘法&#xff0c;是针对与 一个很大的数 * 一个正常的整数 而不是两个超大整数相乘 所以这…

C学习:一个百思不得其解的无符号数移位问题

C学习&#xff1a;一个百思不得其解的无符号数移位问题问题背景验证分析参考资料问题背景 在做一个算法定点化移位过程中&#xff0c;遇到个奇怪问题&#xff1a;分别按无符号数和有符号数进行右移&#xff0c;竟然不管啥输入&#xff0c;res1和res2结果都一样。代码如下&#…

【Android】系统源码的上传

前言 在之前的文章中&#xff0c;写了如何下载系统源码&#xff1a;【Android】系统源码下载及编译 这篇文章就写写系统源码的上传。 为了对 Android 代码质量进行管控&#xff0c;Google 采用 Gerrit 进行 CodeView&#xff0c;并利用 Jenkins 做代码静态检测和自动化验证。…

Redis篇之Redis事务

Redis事务 Redis事务的本质是一组命令的集合 一个事务中所有命令会按照顺序串行化执行队列中的命令&#xff0c;其他客户端提交的命令请求不会插入到事务执行命令序列中。 事务执行三阶段&#xff1a; 开启&#xff1a;以 MULTI 开始一个事务 入队&#xff1a;将多个命令入队…

ToBeWritten之物联网通信接口调试

也许每个人出生的时候都以为这世界都是为他一个人而存在的&#xff0c;当他发现自己错的时候&#xff0c;他便开始长大 少走了弯路&#xff0c;也就错过了风景&#xff0c;无论如何&#xff0c;感谢经历 转移发布平台通知&#xff1a;将不再在CSDN博客发布新文章&#xff0c;敬…

C++ - 多态(1) | 多态的概念、构成条件 、原理

之前的文章中我们讲述了继承有关的知识&#xff0c;在本文中将继续进行C中多态的学习。 多态的概念 多态的概念&#xff1a;通俗来说&#xff0c;就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当不同的对象去完成时会 产生出不同的状态。举个栗子&#xff1…