代理模式及常见的3种代理类型对比

代理模式及常见的3种代理类型对比

  • 代理模式
  • 代理模式分类
    • 静态代理
    • JDK动态代理
    • CGLIB
      • Fastclass机制
  • 三种代理方式之间对比
  • 常见问题

代理模式

代理模式是一种设计模式,提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能

一个比方:在租房的时候,有的人会通过房东直租,有的人会通过中介租房。

这两种情况哪种比较方便呢?当然是通过中介更加方便。

这里的中介就相当于代理,用户通过中介完成租房的一系列操作(看房、交押金、租房、清扫卫生)代理模式可以有效的将具体的实现与调用方进行解耦,通过面向接口进行编码完全将具体的实现隐藏在内部。

在这里插入图片描述

代理模式分类

  • 静态代理: 在编译时就已经实现,编译完成后代理类是一个实际的class文件

  • 动态代理: 在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中

  • 基于JDK的动态代理(反射)

  • 基于CGLIB的动态代理(字节码技术)

静态代理

使用方式:

1.创建一个接口,然后创建被代理的类实现该接口并且实现该接口中的抽象方法。

2.之后再创建一个代理类,同时使其也实现这个接口。

3.在代理类中持有一个被代理对象的引用,而后在代理类方法中调用该对象的方法。

public interface UserDao {void save();
}
public class UserDaoImpl implements UserDao {@Overridepublic void save() {System.out.println("正在保存用户...");}
}
public class TransactionHandler implements UserDao {//目标代理对象private UserDao target;//构造代理对象时传入目标对象public TransactionHandler(UserDao target) {this.target = target;}@Overridepublic void save() {//调用目标方法前的处理System.out.println("开启事务控制...");//调用目标对象的方法target.save();//调用目标方法后的处理System.out.println("关闭事务控制...");}
}
public class Main {public static void main(String[] args) {//新建目标对象UserDaoImpl target = new UserDaoImpl();//创建代理对象, 并使用接口对其进行引用UserDao userDao = new TransactionHandler(target);//针对接口进行调用userDao.save();}
}

使用JDK静态代理很容易就完成了对一个类的代理操作。但是JDK静态代理的缺点也暴露了出来:由于代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐

JDK动态代理

使用JDK动态代理的五大步骤:

1.通过实现InvocationHandler接口来自定义自己的InvocationHandler
2.通过Proxy.getProxyClass获得动态代理类;
3.通过反射机制获得代理类的构造方法,方法签名为getConstructor(InvocationHandler.class)
4.通过构造函数获得代理对象并将自定义的InvocationHandler实例对象传为参数传入;
5.通过代理对象调用目标方法;

public interface IHello {void sayHello();
}public class HelloImpl implements IHello {@Overridepublic void sayHello() {System.out.println("Hello world!");}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class MyInvocationHandler implements InvocationHandler {/** 目标对象 */private Object target;public MyInvocationHandler(Object target){this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("------插入前置通知代码-------------");// 执行相应的目标方法Object rs = method.invoke(target,args);System.out.println("------插入后置处理代码-------------");return rs;}
}
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;public class MyProxyTest {public static void main(String[] args)throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {// =========================第一种==========================// 1、生成$Proxy0的class文件System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");// 2、获取动态代理类Class proxyClazz = Proxy.getProxyClass(IHello.class.getClassLoader(),IHello.class);// 3、获得代理类的构造函数,并传入参数类型InvocationHandler.classConstructor constructor = proxyClazz.getConstructor(InvocationHandler.class);// 4、通过构造函数来创建动态代理对象,将自定义的InvocationHandler实例传入IHello iHello1 = (IHello) constructor.newInstance(new MyInvocationHandler(new HelloImpl()));// 5、通过代理对象调用目标方法iHello1.sayHello();// ==========================第二种=============================/*** Proxy类中还有个将2~4步骤封装好的简便方法来创建动态代理对象,*其方法签名为:newProxyInstance(ClassLoader loader,Class<?>[] instance, InvocationHandler h)*/IHello  iHello2 = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(), // 加载接口的类加载器new Class[]{IHello.class}, // 一组接口new MyInvocationHandler(new HelloImpl())); // 自定义的InvocationHandleriHello2.sayHello();}
}

JDK静态代理与JDK动态代理之间有些许相似,比如说都要创建代理类,以及代理类都要实现接口等。

不同之处: 在静态代理中我们需要对哪个接口和哪个被代理类创建代理类,所以我们在编译前就需要代理类实现与被代理类相同的接口,并且直接在实现的方法中调用被代理类相应的方法;但是动态代理则不同,我们不知道要针对哪个接口、哪个被代理类创建代理类,因为它是在运行时被创建的。

一句话来总结一下JDK静态代理和JDK动态代理的区别:

JDK静态代理是通过直接编码创建的,而JDK动态代理是利用反射机制在运行时创建代理类的。

其实在动态代理中,核心是InvocationHandler。每一个代理的实例都会有一个关联的调用处理程序(InvocationHandler)。对待代理实例进行调用时,将对方法的调用进行编码并指派到它的调用处理器(InvocationHandler)的invoke方法

对代理对象实例方法的调用都是通过InvocationHandler中的invoke方法来完成的,而invoke方法会根据传入的代理对象、方法名称以及参数决定调用代理的哪个方法。

CGLIB

CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类

CGLIB代理实现如下:

  1. 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。
  2. 然后在需要使用的时候,通过CGLIB动态代理获取代理对象。

使用案例

 public class HelloService {public HelloService() {System.out.println("HelloService构造");}/*** 该方法不能被子类覆盖,Cglib是无法代理final修饰的方法的*/final public String sayOthers(String name) {System.out.println("HelloService:sayOthers>>"+name);return null;}public void sayHello() {System.out.println("HelloService:sayHello");}
}
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** 自定义MethodInterceptor*/
public class MyMethodInterceptor implements MethodInterceptor{/*** sub:cglib生成的代理对象* method:被代理对象方法* objects:方法入参* methodProxy: 代理方法*/@Overridepublic Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("======插入前置通知======");Object object = methodProxy.invokeSuper(sub, objects);System.out.println("======插入后者通知======");return object;}
}
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;public class Client {public static void main(String[] args) {// 代理类class文件存入本地磁盘方便我们反编译查看源码System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");// 通过CGLIB动态代理获取代理对象的过程Enhancer enhancer = new Enhancer();// 设置enhancer对象的父类enhancer.setSuperclass(HelloService.class);// 设置enhancer的回调对象enhancer.setCallback(new MyMethodInterceptor());// 创建代理对象HelloService proxy= (HelloService)enhancer.create();// 通过代理对象调用目标方法proxy.sayHello();}
}

JDK代理要求被代理的类必须实现接口,有很强的局限性。

而CGLIB动态代理则没有此类强制性要求。简单的说,CGLIB会让生成的代理类继承被代理类,并在代理类中对代理方法进行强化处理(前置处理、后置处理等)。

总结一下CGLIB在进行代理的时候都进行了哪些工作

  • 生成的代理类继承被代理类。在这里我们需要注意一点:如果委托类被final修饰,那么它不可被继承,即不可被代理;同样,如果委托类中存在final修饰的方法,那么该方法也不可被代理
  • 代理类会为委托方法生成两个方法,一个是与委托方法签名相同的方法,它在方法中会通过super调用委托方法;另一个是代理类独有的方法
  • 当执行代理对象的方法时,会首先判断一下是否存在实现了MethodInterceptor接口的CGLIB$CALLBACK_0;,如果存在,则将调用MethodInterceptor中的intercept方法

intercept方法中,我们除了会调用委托方法,还会进行一些增强操作。在Spring AOP中,典型的应用场景就是在某些敏感方法执行前后进行操作日志记录

在CGLIB中,方法的调用并不是通过反射来完成的,而是直接对方法进行调用:通过FastClass机制对Class对象进行特别的处理,比如将会用数组保存method的引用,每次调用方法的时候都是通过一个index下标来保持对方法的引用

Fastclass机制

CGLIB采用了FastClass的机制来实现对被拦截方法的调用。

FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法

public class test10 {//这里,tt可以看作目标对象,fc可以看作是代理对象;首先根据代理对象的getIndex方法获取目标方法的索引,//然后再调用代理对象的invoke方法就可以直接调用目标类的方法,避免了反射public static void main(String[] args){Test tt = new Test();Test2 fc = new Test2();int index = fc.getIndex("f()V");fc.invoke(index, tt, null);}
}class Test{public void f(){System.out.println("f method");}public void g(){System.out.println("g method");}
}
class Test2{public Object invoke(int index, Object o, Object[] ol){Test t = (Test) o;switch(index){case 1:t.f();return null;case 2:t.g();return null;}return null;}//这个方法对Test类中的方法建立索引public int getIndex(String signature){switch(signature.hashCode()){case 3078479:return 1;case 3108270:return 2;}return -1;}
}

上例中,Test2是Test的Fastclass,在Test2中有两个方法getIndex和invoke。

在getIndex方法中对Test的每个方法建立索引,并根据入参(方法名+方法的描述符)来返回相应的索引。

Invoke根据指定的索引,以ol为入参调用对象O的方法。这样就避免了反射调用,提高了效率

三种代理方式之间对比

这里对三钟代理方式做一个简单对比:

代理方式实现优点缺点特点
JDK静态代理代理类与委托类实现同一接口,并且在代理类中需要硬编码接口实现简单,容易理解代理类需要硬编码接口,在实际应用中可能会导致重复编码,浪费存储空间并且效率很低好像没啥特点
JDK动态代理代理类与委托类实现同一接口,主要是通过代理类实现InvocationHandler并重写invoke方法来进行动态代理的,在invoke方法中将对方法进行增强处理不需要硬编码接口,代码复用率高只能够代理实现了接口的委托类底层使用反射机制进行方法的调用
CGLIB动态代理代理类将委托类作为自己的父类并为其中的非final委托方法创建两个方法,一个是与委托方法签名相同的方法,它在方法中会通过super调用委托方法;另一个是代理类独有的方法。在代理方法中,它会判断是否存在实现了MethodInterceptor接口的对象,若存在则将调用intercept方法对委托方法进行代理可以在运行时对类或者是接口进行增强操作,且委托类无需实现接口不能对final类以及final方法进行代理底层将方法全部存入一个数组中,通过数组索引直接进行方法调用

常见问题

CGlib比JDK快?

  • 使用CGLiB实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类, 在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理, 因为CGLib原理是动态生成被代理类的子类。
  • 在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率。只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐。

Spring如何选择用JDK还是CGLIB?

  • 当Bean实现接口时,Spring就会用JDK的动态代理。
  • 当Bean没有实现接口时,Spring使用CGlib实现。
  • 可以强制使用CGlib

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

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

相关文章

嵌入式开发实用工具——QFSViewer

嵌入式开发实用工具——QFSViewer 介绍 今天给大家推荐个我个人业余时间开发的一个嵌入式开发实用工具——QFSViewer&#xff0c;这个工具主要是用来加载查看各种嵌入式常用的文件系统映像&#xff0c;目前支持JFSS2、Fat32、Fat16、Fat12、exFat、Ext2、Ext3、Ext4等文件系统…

用栈判断是否匹配

1 问题 写代码的时候用到的括号都是成双成对的出现&#xff0c;并且大小也相同。在集成编辑环境中&#xff0c;IDE就会为我们自己动检查括号是否匹配。那么为了避免在报错&#xff0c;如何判断是否有无括号不匹配&#xff1f; 2 方法 利用栈来实现这种功能。当遇见一个左括号&a…

【Linux命令行与Shell脚本编程】 第十七章 图形化桌面环境脚本编程

Linux命令行与Shell脚本编程 第十七章 图形化桌面环境脚本编程 文章目录 Linux命令行与Shell脚本编程七.图形化桌面环境脚本编程7.1.创建文本菜单7.1.1.创建菜单布局7.1.2.创建菜单逻辑7.1.3.整合脚本菜单7.1.4.使用select命令 7.2.创建文本窗口部件7.2.1.dialog软件包部件msg…

wpf 项目中使用 Prism + MaterialDesign

1.通过nuget安装MaterialDesign 2.通过nuget安装Prism 3.修改App.xmal <prism:PrismApplication x:Class"VisionMeasureGlue.App"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/…

66 # form 数据格式化

实现一个 http 服务器 客户端会发送请求 GET POST 要处理不同的请求体的类型 表单格式&#xff08;formData a1&b2&#xff09;&#xff0c;可以直接通信不会出现跨域问题JSON &#xff08;"{"kaimo":"313"}"&#xff09;文件格式 &#x…

Android 项目导入高德SDK初次上手

文章目录 一、前置知识&#xff1a;二、学习目标三、学习资料四、操作过程1、创建空项目2、高德 SDK 环境接入2.1 获取高德 key2.2下载 SDK 并导入2.2.1、下载SDK 文件2.2.2、SDK 导入项目2.2.3、清单文件配置2.2.4、隐私权限 3、显示地图 一、前置知识&#xff1a; 1、Java 基…

移动端自动化测试实战

UI自动化测试的价值 1、提升回归测试的效率 2、可以进行兼容性测试 UI 自动化测试应用场景 • 冒烟测试自动化&#xff1a;提测之前自动断言提测质量&#xff0c;提供准入参考。 • 功能测试自动化&#xff1a;辅助 QA 与测试工程师的快速验证。 • 验收测试自动化&#xf…

stable-diffusion-webui 界面汉化

本教程通过安装 sd-webui-bilingual-localization 插件来达到汉化目的, 项目地址为:https://github.com/journey-ad/sd-webui-bilingual-localization 一、安装插件 先进入插件安装界面 在搜索栏搜索 zh_CN Localization 中文语言包, 项目地址: https://github.com/dtlnor/st…

CrossOver是什么软件 CrossOver软件好用吗

CrossOver是一款由CodeWeavers公司开发的软件&#xff0c;它可以在Mac和Linux等操作系统上运行Windows软件&#xff0c;而无需在计算机上安装Windows操作系统。这款软件的核心技术是Wine&#xff0c;它是一种在Linux和macOS等操作系统上运行Windows应用程序的开源软件。本文将会…

使用docker 搭建nginx + tomcat 集群

创建3个Tomcat容器&#xff0c;端口分别映射到 8080,8081,8082&#xff0c;使用数据卷挂载&#xff0c;分别将宿主机目录下的 /opt/module/docker/tomcat3/ROOT1/&#xff0c;/opt/module/docker/tomcat3/ROOT2/&#xff0c;/opt/module/docker/tomcat3/ROOT2/ 挂载到 容器内部…

CAD练习——绘制冲压件三视图

首先还是先设置咱们的绘图模板&#xff1a; 这是图层划分&#xff1a; 文字样式设置&#xff1a; 标注样式&#xff1a; 从主视图开始&#xff0c;首先绘制如下图形 用到的快捷指令&#xff1a; L&#xff1a;直线 O&#xff1a;偏移 TR&#xff1a;修剪 效果&#xff1a;…

搭建日志服务器Rsyslog

Rsyslog介绍 Rsyslog的全称是 rocket-fast system for log&#xff0c;它提供了高性能&#xff0c;高安全功能和模块化设计。rsyslog能够接受从各种各样的来源&#xff0c;将其输入&#xff0c;输出的结果到不同的目的地。rsyslog可以提供超过每秒一百万条消息给目标文件。 特…

SQL Server数据库如何添加Oracle链接服务器(Windows系统)

SQL Server数据库如何添加Oracle链接服务器 一、在添加访问Oracle的组件1.1 下载Oracle的组件 Oracle Provider for OLE DB1.2 注册该组件1.2.1 下载的压缩包解压位置1.2.2 接着用管理员运行Cmd 此处一定要用管理员运行&#xff0c;否则会报错 二、配置环境变量三、 重启SQL Se…

使用Spring五大注解来更加简单的存储Bean对象

在使用Spring框架的时候我们如果使用这种方式来存储bean对象的话未免有点太麻烦了 <bean id"xxx" class"xxx"> </bean> 为了简化存储Bean对象的操作&#xff0c;我们可以使用五大类注解来进行存储Bean对象 我们首先要在配置文件配置扫描路径…

【快应用】list组件属性的运用指导

【关键词】 list、瀑布流、刷新、页面布局 【问题背景】 1、 页面部分内容需要瀑布流格式展示&#xff0c;在使用lsit列表组件设置columns进行多列渲染时&#xff0c;此时在里面加入刷新动画时&#xff0c;动画只占了list组件的一列&#xff0c;并没有完全占据一行宽度&…

把大模型装进手机,分几步?

点击关注 文 | 姚 悦 编 | 王一粟 大模型“跑”进手机&#xff0c;AI的战火已经从“云端”烧至“移动终端”。 “进入AI时代&#xff0c;华为盘古大模型将会来助力鸿蒙生态。”8月4日&#xff0c;华为常务董事、终端BG CEO、智能汽车解决方案BU CEO 余承东介绍&#xff0c…

Gson 添加数据默认值问题记录

问题&#xff1a;在用Gson add(key&#xff08;string类型&#xff09;&#xff0c;value&#xff08;必须是JsonElement子类&#xff09;&#xff09;时发现&#xff0c;value 传了 "" 空字符串&#xff08;非null&#xff09;&#xff0c;默认解析后返回null&#…

jmeter测试rpc接口-使用dubbo框架调用【杭州多测师_王sir】

1.基于SOAP架构。基于XML规范。基于WebService协议。特点:接口地址?wsdl结尾2.基于RPC架构&#xff0c;基于dubbo协议&#xff0c;thrift协议。SpringCloud微服务。3.基于RestFul架构&#xff0c;基于json规范。基于http协议(我们常用的都是这种&#xff0c;cms平台也是) Rest…

windows环境下安装elasticsearch、kibana

通过本文可以快速在windows系统上安装elasticsearch、kibana环境。 当你用Integer类型的时候&#xff0c;要非常小心&#xff0c;因为100等于100、但是200不等于200&#xff0c;当然&#xff0c;如果你会一点小花招&#xff0c;也可以让100不等于100、让200等于200。(运算符比较…

CSS 盒模型是什么?它包含哪些属性?标准盒模型/怪异盒模型

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 盒模型⭐ 标准盒模型⭐ 怪异盒模型⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感…