静态代理,JDK动态代理,CGLIB代理原理详解

学习代理前要对反射有一定的了解

代理: 

代理是一种设计模式,代理模式是一种结构型设计模式,它允许通过创建一个代理对象来控制对另一个对象的访问。代理对象充当了客户端与真实对象之间的中介,它可以在客户端和真实对象之间添加额外的功能或控制访问方式。

代理模式通常通过创建一个共同的接口或抽象类来定义代理对象和真实对象的公共行为。这使得客户端可以在不知道真实对象的情况下直接与代理对象进行交互。

代理模式的优点包括:

  • 对客户端透明:客户端可以不知道真实对象的存在,只与代理对象进行交互。
  • 代理对象可以在不影响真实对象的情况下,添加额外的功能。
  • 可以实现对真实对象的控制和保护。

然而,代理模式也有一些缺点,包括:

  • 增加了系统的复杂性,引入了额外的代码。
  • 可能会降低系统的性能,因为代理对象需要执行额外的操作。

静态代理: 

静态代理是代理模式的一种实现方式,它通过创建一个代理类来控制对真实对象的访问。在静态代理中,代理类和真实类都实现相同的接口或继承相同的父类,代理类持有一个对真实类的引用,并在调用真实对象的方法之前或之后执行一些额外的逻辑

先介绍一下代码结构

task接口有一个work方法,以及一个Strign类型的参数

public interface task {void work(String name);
}

Student继承自work方法同时作为被代理的对象

public class Student implements task{@Overridepublic void work(String name) {System.out.println("学生正在做"+name+"作业");}
}

代码运行

  public static void main(String[] args) {Student s = new Student();staticJdk staticJdk = new staticJdk(s);staticJdk.work("语文");}这是静态代理学生正在做语文作业

这就是静态代理,相当于是多了一层调用

需要注意的是,静态代理的缺点在于每个代理类只能代理一个具体的接口或类,如果需要代理的接口或类很多,就需要编写大量的代理类。此外,静态代理在编译时就确定了代理关系,无法动态地修改代理对象。为了解决这些问题,可以使用动态代理。

JDK动态代理

首先动态代理是由Proxy类调用newProxyInstance方法生成,需要三个参数分别是

类加载器(在运行时动态地生成代理类的字节码并加载到内存中),被代理对象的接口的类型(能够代理的方法来自于接口),以及InvocationHandler的实现类(代理的真正逻辑)

接口和被代理对象不变

多了一个InvocationHandler实现类

main方法 

 

 Student student = new Student();handler h = new handler(student);task o = (task) Proxy.newProxyInstance(Student.class.getClassLoader(), Student.class.getInterfaces(), h);o.work("语文");

这里o就是生成的代理对象,效果还是一样,但是这解决了静态代理中接口如果改变代理类也要改变的缺点

JDK动态代理的原理

先介绍一个类

ProxyGenerator 是 Java 中的一个类,它位于 sun.misc 包下。它是一个用于生成代理类字节码的工具类。该类提供了一些静态方法,可以用来生成代理类的字节码,并将其保存到磁盘上的 .class 文件中

我把生成的字节码写出到文件中

这个类继承自Proxy实现了task接口,这里的m1 ,m2 ,m3 ...都是反射中Method对象

这个类中有一个静态代码块通过反射获得Method对象

 static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m4 = Class.forName("task").getMethod("a");m2 = Class.forName("java.lang.Object").getMethod("toString");m3 = Class.forName("task").getMethod("work", 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());}}

可以看到除了Object类的方法外还多了接口中的方法这就是JDK动态代理只能代理接口中方法的原因

当我们在main方法中通过代理对象调用work方法时

这里的super是Proxy,h是InvocationHandler,再调用invoke的实现方法

思路返回到自己的InvocationHandler中

这里的proxy就是生成的代理对象,Method就是代理类传递的通过反射获得的接口中的方法,args相当于参数。

JDK动态代理是通过Java的反射机制实现的,虽然它提供了一种方便的方式来创建代理对象,但也存在一些缺陷和限制。

以下是JDK动态代理的一些主要缺陷:

  1. 只能代理接口:JDK动态代理只能代理接口,无法代理具体的类。这是由于JDK动态代理是基于接口的,它生成的代理类实现了目标接口,并通过实现接口中的方法来实现代理逻辑。如果需要代理的是具体的类而不是接口,就无法使用JDK动态代理,需要考虑其他的代理方式。

  2. 无法操作非公共方法:JDK动态代理只能代理目标接口中的公共方法,对于非公共方法无法进行代理。这是因为生成的代理类只能访问目标接口中的公共方法,无法访问目标类中的非公共方法。如果需要代理的方法是非公共方法,就无法使用JDK动态代理。

  3. 性能相对较低:相比于静态代理或CGLIB等其他代理方式,JDK动态代理的性能通常较低。这是因为在运行时,每次调用代理对象的方法时都需要通过反射机制来执行相应的逻辑,包括查找方法、参数转换等操作,相比直接调用目标对象的方法会有一定的性能损耗。

  4. 无法绕过final方法和类:JDK动态代理无法代理被final修饰的方法和类。由于final方法和类无法被继承或覆盖,因此无法在代理类中生成对应的代理逻辑。

  5. 无法直接访问目标对象:JDK动态代理通过代理对象间接地调用目标对象的方法,无法直接访问目标对象。这意味着在代理对象中无法使用this关键字来引用目标对象,也无法在代理对象中直接调用目标对象的方法。

CGLIB动态代理

CGLIB(Code Generation Library)是一个开源的第三方库,用于在Java中生成动态代理类。与JDK动态代理不同,CGLIB可以代理具体的类而不仅限于接口。

CGLIB通过操作目标类的字节码来生成代理类的字节码,并在运行时加载和实例化代理类。它使用了字节码操作库ASM来分析目标类的字节码,并生成一个新的字节码类。生成的字节码类继承自目标类,并重写了所需的方法,以实现代理逻辑。

代码介绍 

Student作为被代理对象

public class Student {public int work(){System.out.println("正在计算");System.out.println("计算完成");return 1;}
}

tansaction对象提供增强方法

public class tansaction {public void before(){System.out.println("完成初始化");}public void end(){System.out.println("完成资源释放");}
}

需要先导入CGLIB的依赖包

接下来是主代码 

先定义一个方法拦截器继承 MethodInterceptor

public class cglibInterceptor implements MethodInterceptor {//提供增强方法的对象tansaction t;public cglibInterceptor(tansaction t){this.t =t ;}//代码的实际逻辑@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {//MethodProxy是方法的代理类t.before();//调用父类的方法  Object o1 = methodProxy.invokeSuper(o, objects);t.end();return o1;}
}

main方法

CGLIB原理

先看生成的调试信息

这个代理类继承自Student类(main方法的enhancer的setSuperclass 方法)

代理类中有一个work方法

这个方法首先为 MethodInterceptor var10000 赋值

然后调用MethodInterceptor的 intercept 方法的实现 

这里的this是指继承自Student类的当前对象

即我们自定义的方法

通过 invokeSuper 方法调用当前类的父类的方法并传入参数 这里相当于调用父类Student的work方法,从而通过继承实现了动态代理

CGLIB的优点是可以代理具体的类,不受接口的限制,同时还能够绕过final方法和类的限制。然而,由于CGLIB使用了字节码操作和反射,相对于JDK动态代理,它的性能开销可能会稍高一些。

缺点基于继承,使用CGLIB生成的代理类是目标类的子类,因此无法代理被声明为final的类。此外,CGLIB无法代理static方法和final方法,因为它们无法被继承或覆盖。

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

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

相关文章

MySQL笔记-第08章_聚合函数

视频链接:【MySQL数据库入门到大牛,mysql安装到优化,百科全书级,全网天花板】 文章目录 第08章_聚合函数1. 聚合函数介绍1.1 AVG和SUM函数1.2 MIN和MAX函数1.3 COUNT函数 2. GROUP BY2.1 基本使用2.2 使用多个列分组2.3 GROUP BY中…

现代雷达车载应用——第2章 汽车雷达系统原理 2.2节

经典著作,值得一读,英文原版下载链接【免费】ModernRadarforAutomotiveApplications资源-CSDN文库。 2.2 汽车雷达架构 从顶层来看,基本的汽车雷达由发射器,接收器和天线组成。图2.2给出了一种简化的单通道连续波雷达结构[2]。这…

Excel——TEXTJOIN函数实现某一列值相等时合并其他列

一、TEXTJOIN函数介绍 公式TEXTJOIN(分隔符, 忽略空白单元格, 字符串1…) 分隔符:文本字符串,或者为空,或用双引号引起来的一个或多个字符,或对有效文本字符串的引用。如果提供一个数字,则将被视为文本。 忽略空白单…

Python 神奇解码器:pyWhat 库全面指南

更多资料获取 📚 个人网站:ipengtao.com 在当今数字化的世界中,理解和处理文本数据是许多应用程序的关键任务。而PyWhat库作为一个用于处理文本的Python库,提供了强大的功能,帮助开发者在文本中识别和提取有意义的信息…

deepface:实现人脸的识别和分析

deepface介绍 deepface能够实现的功能 人脸检测:deepface 可以在图像中检测出人脸的位置,为后续的人脸识别任务提供基础。 人脸对齐:为了提高识别准确性,deepface 会将检测到的人脸进行对齐操作,消除姿态、光照和表…

青蛙跳台阶(C语言)

1.代码: 2.问题:青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个n级的台阶总共有多少种跳法? 3.答案: 我们用递归方法来解题: 秉持着大事化小原则: 假设让青蛙跳上一个台阶,那么还有(n - 1)个台阶要跳 假设让青蛙跳上二个台阶,那么还…

包装类 和 初阶泛型(详解)

【本节目标】 1. 以能阅读 java 集合源码为目标学习泛型 2. 掌握包装类 3. 掌握泛型 1. 包装类 在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类型。 除了Integer和Charact…

二百一十六、Flume——Flume拓扑结构之负载均衡和故障转移的开发案例(亲测,附截图)

一、目的 对于Flume的负载均衡和故障转移拓扑结构,进行一个开发测试 二、负载均衡和故障转移 (一)结构含义 Flume支持使用将多个sink逻辑上分到一个sink组 (二)结构特征 sink组配合不同的SinkProcessor可以实现负…

SpringBoot--入门使用

目录 SpringBoot简介 什么是SpringBoot 相比Spring,SpringBoot的有哪些特点 SpringBoot入门使用 创建SpringBoot项目 配置项目名称 启动SpringBoot SpringBoot整合依赖,配置开发环境 SpringBoot整合jdbc SpringBoot整合mybatis 配置开启log日志…

隔离电源与非隔离式电源

开关电源 文章目录 开关电源前言一、它们之间的区别是什么?二、如何区分它们呢?三、隔离电源与非隔离电源的优缺点四、隔离电源与非隔离电源的选择总结 前言 在产品设计时,倘若没有考虑应用环境对电源隔离的要求,产品到了应用时就…

Linux服务器安装vim命令

1、查看是否安装vim命令 vim /etc/hosts2、检查系统中是否存在安装包 rpm -qa|grep vim2、 安装vim yum -y install vim*4、测试是否安装成功 vim /etc/hosts

linux交换分区管理SWAP

概念查看当前的交换分区:free 6.2.5 交换分区管理SWAP 6.2.5.1 概念 作用: ”提升“内存容量,防止OOM(out of memory,内存溢出)。 ​ 对应windows中的虚拟内存。 ​ 从功能上讲,交换分区主要是…

Java进阶 1-1 枚举

目录 枚举的基本特性 枚举类型中的自定义方法 switch语句中的枚举 编译器创建的values()方法 使用实现代替继承 构建工具:生成随机的枚举 组织枚举 EnumSet EnumMap 本笔记参考自: 《On Java 中文版》 枚举类型通过enum关键字定义,其…

【从零开始学习JVM | 第六篇】快速了解 直接内存

前言: 当谈及Java虚拟机(JVM)的内存管理时,我们通常会想到堆内存和栈内存。然而,还有一种被称为"直接内存"的特殊内存区域,它在Java应用程序中起着重要的作用。直接内存提供了一种与Java堆内存和…

三天搞定jmeter入门到入职全套教程之使用Jmeter录制脚本

相对于LoadRunner跟SilkPerformer来说,Jmeter确实有差距,但毕竟前两者太贵,Jmeter胜在免费开源。 先看下LoadRunner录制的脚本如下,美如画,结构清晰,易于修改编辑,比如做关联等。当然目前LoadR…

Java项目-瑞吉外卖Day2

完善登录功能: 完善未登录不能访问/backend/index.html。使用拦截器或过滤器。 创建过滤器。 重写doFilter方法。 查看是否过滤成功。 处理流程如下: 添加员工功能: 点击保存,可以看到请求信息。 再看前端代码&a…

C++学习笔记—— C++内存管理方式:new和delete操作符进行动态内存管理

系列文章目录 http://t.csdnimg.cn/d0MZH 目录 系列文章目录http://t.csdnimg.cn/d0MZH 比喻和理解a.比喻C语言开空间C开空间 b.理解a、C语言的内存管理的缺点1、开发效率低(信息传递繁琐)2、可读性低(信息展示混乱)3、稳定性差&…

中间件系列 - Redis入门到实战(基础篇)

前言 1.学习视频: 黑马程序员Redis入门到实战教程,深度透析redis底层原理redis分布式锁企业解决方案黑马点评实战项目 2. 本内容仅用于个人学习笔记,如有侵扰,联系删除 3. 本章学习目标: 初始Redis 认识NoSQL认识Redi…

C++STL库的 deque、stack、queue、list、set/multiset、map/multimap

deque 容器 Vector 容器是单向开口的连续内存空间, deque 则是一种双向开口的连续线性空 间。所谓的双向开口,意思是可以在头尾两端分别做元素的插入和删除操作,当然, vector 容器也可以在头尾两端插入元素,但是在其…

【LeetCode刷题-树】-- 116.填充每个节点的下一个右侧节点指针

116.填充每个节点的下一个右侧节点指针 方法:层次遍历 /* // Definition for a Node. class Node {public int val;public Node left;public Node right;public Node next;public Node() {}public Node(int _val) {val _val;}public Node(int _val, Node _left, N…