JAVA设计模式之代理模式详解

代理模式

1 代理模式介绍

在软件开发中,由于一些原因,客户端不想或不能直接访问一个对象,此时可以通过一个称为"代理"的第三者来实现间接访问.该方案对应的设计模式被称为代理模式.

代理模式(Proxy Design Pattern ) 原始定义是:让你能够提供对象的替代品或其占位符。代理控制着对于原对象的访问,并允许将请求提交给对象前后进行一些处理。

  • 现实生活中的代理: 海外代购

在这里插入图片描述

  • 软件开发中的代理

    代理模式中引入了一个新的代理对象,代理对象在客户端对象和目标对象之间起到了中介的作用,它去掉客户不能看到的内容和服务或者增加客户需要的额外的新服务.

2 代理模式原理

代理(Proxy)模式分为三种角色:

  • 抽象主题(Subject)类: 声明了真实主题和代理主题的共同接口,这样就可以保证任何使用真实主题的地方都可以使用代理主题,客户端一般针对抽象主题类进行编程。
  • 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以在任何时候访问、控制或扩展真实主题的功能。
  • 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。

在这里插入图片描述

3 静态代理实现

这种代理方式需要代理对象和目标对象实现一样的接口。

  • 优点:可以在不修改目标对象的前提下扩展目标对象的功能。

  • 缺点:

    1. 冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。

    2. 不易维护。一旦接口增加方法,目标对象与代理对象都要进行修改。

举例:保存用户功能的静态代理实现

//接口类: IUserDao
public interface IUserDao {void save();
}
//目标对象:UserDaoImpl
public class UserDaoImpl implements IUserDao {@Overridepublic void save() {System.out.println("保存数据");}
}//静态代理对象:UserDaoProxy 需要实现IUserDao接口
public class UserDaoProxy implements IUserDao {private IUserDao target;public UserDaoProxy(IUserDao target) {this.target = target;}@Overridepublic void save() {System.out.println("开启事务"); //扩展额外功能target.save();System.out.println("提交事务");}
}//测试类
public class TestProxy {@Testpublic void testStaticProxy(){//目标对象UserDaoImpl userDao = new UserDaoImpl();//代理对象UserDaoProxy proxy = new UserDaoProxy(userDao);proxy.save();}
}

4 JDK动态代理

4.1 JDK动态代理实现

动态代理利用了JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能.动态代理又被称为JDK代理或接口代理.

静态代理与动态代理的区别:

  1. 静态代理在编译时就已经实现了,编译完成后代理类是一个实际的class文件
  2. 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中.

JDK中生成代理对象主要涉及的类有

  • java.lang.reflect Proxy,主要方法为
static Object newProxyInstance(ClassLoader loader,  		//指定当前目标对象使用类加载器Class<?>[] interfaces,    //目标对象实现的接口的类型InvocationHandler h      //事件处理器
) 
//返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
  • java.lang.reflect InvocationHandler,主要方法为

    Object invoke(Object proxy, Method method, Object[] args) 
    // 在代理实例上处理方法调用并返回结果。
    

举例:保存用户功能的静态代理实现

/*** 代理工厂-动态生成代理对象**/
public class ProxyFactory {private Object target; //维护一个目标对象public ProxyFactory(Object target) {this.target = target;}//为目标对象生成代理对象public Object getProxyInstance(){//使用Proxy获取代理对象return Proxy.newProxyInstance(target.getClass().getClassLoader(), //目标类使用的类加载器target.getClass().getInterfaces(), //目标对象实现的接口类型new InvocationHandler(){ //事件处理器/*** invoke方法参数说明* @param proxy 代理对象* @param method 对应于在代理对象上调用的接口方法Method实例* @param args 代理对象调用接口方法时传递的实际参数* @return: java.lang.Object 返回目标对象方法的返回值,没有返回值就返回null*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("开启事务");//执行目标对象方法method.invoke(target, args);System.out.println("提交事务");return null;}});}}//测试
public static void main(String[] args) {IUserDao target = new UserDaoImpl();System.out.println(target.getClass());//目标对象信息IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();System.out.println(proxy.getClass()); //输出代理对象信息proxy.save(); //执行代理方法
}
4.2 类是如何动态生成的

Java虚拟机类加载过程主要分为五个阶段:加载、验证、准备、解析、初始化。其中加载阶段需要完成以下3件事情:

  1. 通过一个类的全限定名来获取定义此类的二进制字节流
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据访问入口

由于虚拟机规范对这3点要求并不具体,所以实际的实现是非常灵活的,关于第1点,获取类的二进制字节流(class字节码)就有很多途径:

在这里插入图片描述

  • 从本地获取

  • 从网络中获取

  • 运行时计算生成,这种场景使用最多的是动态代理技术,在 java.lang.reflect.Proxy 类中,就是用了 ProxyGenerator.generateProxyClass 来为特定接口生成形式为 *$Proxy 的代理类的二进制字节流

在这里插入图片描述

所以,动态代理就是想办法,根据接口或目标对象,计算出代理类的字节码,然后再加载到JVM中使用

4.3 代理类的调用过程

我们通过借用阿里巴巴的一款线上监控诊断产品 Arthas(阿尔萨斯) ,对动态生成的代理类代码进行查看

67

在这里插入图片描述

代理类代码如下:

package com.sun.proxy;import com.demo.proxy.example01.IUserDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy0
extends Proxy
implements IUserDao {private static Method m1;private static Method m3;private static Method m2;private static Method m0;public $Proxy0(InvocationHandler invocationHandler) {super(invocationHandler);}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m3 = Class.forName("com.demo.proxy.example01.IUserDao").getMethod("save", new Class[0]);m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);return;}catch (NoSuchMethodException noSuchMethodException) {throw new NoSuchMethodError(noSuchMethodException.getMessage());}catch (ClassNotFoundException classNotFoundException) {throw new NoClassDefFoundError(classNotFoundException.getMessage());}}public final boolean equals(Object object) {try {return (Boolean)this.h.invoke(this, m1, new Object[]{object});}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final String toString() {try {return (String)this.h.invoke(this, m2, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final int hashCode() {try {return (Integer)this.h.invoke(this, m0, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final void save() {try {this.h.invoke(this, m3, null);return;}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}
}

简化后的代码

package com.sun.proxy;import com.demo.proxy.example01.IUserDao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy0
extends Proxy
implements IUserDao {private static Method m3;public $Proxy0(InvocationHandler invocationHandler) {super(invocationHandler);}static {try {m3 = Class.forName("com.demo.proxy.example01.IUserDao").getMethod("save", new Class[0]);return;}}public final void save() {try {this.h.invoke(this, m3, null);return;}}
}
  • 动态代理类对象 继承了 Proxy 类,并且实现了被代理的所有接口,以及equals、hashCode、toString等方法

  • 代理类的构造函数,参数是InvocationHandler实例,Proxy.newInstance方法就是通过这个构造函数来创建代理实例的

  • 类和所有方法都被 public final 修饰,所以代理类只可被使用,不可以再被继承

  • 每个方法都有一个 Method 对象来描述,Method 对象在static静态代码块中创建,以 m + 数字 的格式命名

  • 调用方法的时候通过 this.h.invoke(this, m3, null)); 实际上 h.invoke就是在调用ProxyFactory中我们重写的invoke方法

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("开启事务");//执行目标对象方法method.invoke(target, args);System.out.println("提交事务");return null;
    }
    

5 cglib动态代理

5.1 cglib动态代理实现

cglib (Code Generation Library ) 是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。cglib 为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。

在这里插入图片描述

  • 最底层是字节码
  • ASM是操作字节码的工具
  • cglib基于ASM字节码工具操作字节码(即动态生成代理,对方法进行增强)
  • SpringAOP基于cglib进行封装,实现cglib方式的动态代理

使用cglib 需要引入cglib 的jar包,如果你已经有spring-core的jar包,则无需引入,因为spring中包含了cglib 。

  • cglib 的Maven坐标
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.2.5</version>
</dependency>

示例代码

目标类

public class UserServiceImpl {public List<User> findUserList(){return Collections.singletonList(new User("tom",18));}
}public class User {private String name;private int age;.....
}

cglib代理类,需要实现MethodInterceptor接口,并指定代理目标类target

public class UserLogProxy implements MethodInterceptor {private Object target;public UserLogProxy(Object target) {this.target = target;}public Object getLogProxy(){//增强器类,用来创建动态代理类Enhancer en = new Enhancer();//设置代理类的父类字节码对象en.setSuperclass(target.getClass());//设置回调: 对于代理类上所有的方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦截en.setCallback(this);//创建动态代理对象并返回return en.create();}/*** 实现回调方法* @param o     代理对象* @param method  目标对象中的方法的Method实例* @param args      实际参数* @param methodProxy  代理对象中的方法的method实例* @return: java.lang.Object*/@Overridepublic Object intercept(Object o, Method method, Object[] args,MethodProxy methodProxy) throws Throwable {Calendar calendar = Calendar.getInstance();SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");System.out.println(formatter.format(calendar.getTime()) + " [" +method.getName() + "] 查询用户信息...]");Object result = methodProxy.invokeSuper(o, args);return result;}
}
public class Client {public static void main(String[] args) {//目标对象UserServiceImpl userService = new UserServiceImpl();System.out.println(userService.getClass());//代理对象UserServiceImpl proxy = (UserServiceImpl) new UserLogProxy(userService).getLogProxy();System.out.println(proxy.getClass());List<User> userList = proxy.findUserList();System.out.println("用户信息: "+userList);}
}
5.2 cglib代理流程

在这里插入图片描述

6 代理模式总结

6.1 三种代理模式实现方式的对比
  • jdk代理和CGLIB代理

    使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。

    在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。

  • 动态代理和静态代理

    动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

    如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题

6.2 代理模式优缺点

优点:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

缺点:

  • 增加了系统的复杂度;
6.2 代理模式使用场景
  • 功能增强

    当需要对一个对象的访问提供一些额外操作时,可以使用代理模式

  • 远程(Remote)代理

    实际上,RPC 框架也可以看作一种代理模式,GoF 的《设计模式》一书中把它称作远程代理。通过远程代理,将网络通信、数据编解码等细节隐藏起来。客户端在使用 RPC 服务的时候,就像使用本地函数一样,无需了解跟服务器交互的细节。除此之外,RPC 服务的开发者也只需要开发业务逻辑,就像开发本地使用的函数一样,不需要关注跟客户端的交互细节。

  • 防火墙(Firewall)代理

    当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。

  • 保护(Protect or Access)代理

    控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。

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

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

相关文章

IT行业针对大数据的安全文件传输的重要性

在数字化浪潮的推动下&#xff0c;数据已成为现代社会的宝贵资源。特别是大数据&#xff0c;以其海量、多样化、高速增长和低价值密度的特性&#xff0c;对信息技术&#xff08;IT&#xff09;行业产生了深远影响。大数据的应用不仅推动了云计算、物联网和人工智能等领域的发展…

Vuex介绍和使用

1. 什么是Vuex Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式和库。它解决了在大型 Vue.js 应用程序中共享和管理状态的问题&#xff0c;使得状态管理变得更加简单、可预测和可维护。 在 Vue.js 应用中&#xff0c;组件之间的通信可以通过 props 和事件进行&#xff0c…

University Program VWF仿真步骤__全加器

本教程将以全加器为例&#xff0c;选择DE2-115开发板的Cyclone IV EP4CE115F29C7 FPGA&#xff0c;使用Quartus Lite v18.1&#xff0c;循序渐进的介绍如何创建Quartus工程&#xff0c;并使用Quartus Prime软件的University Program VWF工具创建波形文件&#xff0c;对全加器的…

机器学习-线性回归法

线性回归算法 解决回归问题思想简单&#xff0c;实现容易许多强大的非线性模型的基础结果具有很好的可解释性蕴含机器学习中的很多重要思想 样本特征只有一个&#xff0c;称为&#xff1a;简单线性回归 通过分析问题&#xff0c;确定问题的损失函数或者效用函数 通过最优化…

UDP端口探活的那些细节

一 背景 商业客户反馈用categraf的net_response插件配置了udp探测, 遇到报错了&#xff0c;如图 udp是无连接的&#xff0c;无法用建立连接的形式判断端口。 插件最初的设计是需要配置udp的发送字符&#xff0c;并且配置期望返回的字符串&#xff0c; [[instances]] targets…

Java写标准输出进度条

学Java这么久了&#xff0c;突发奇想写一个 进度条 玩玩&#xff0c;下面展示一下成功吧&#xff01; Java代码实现如下 public class ProcessBar {public static void main(String[] args) {//进度条StringBuilder processBarnew StringBuilder();//进度条长度int total100;/…

2024.2.5 vscode连不上虚拟机,始终waiting for server log

昨天还好好的&#xff0c;吃着火锅&#xff0c;做着毕设&#xff0c;突然就被vscode给劫了。 起初&#xff0c;哥们跟着网上教程有模有样地删除了安装包缓存&#xff0c;还删除了.vscode-server&#xff0c;发现没卵用&#xff0c;之前都是搜那个弹窗报错。 后来发现原来是vsco…

问题:塑瓷后的牙冠要比完成的牙冠大() #学习方法#其他

问题&#xff1a;塑瓷后的牙冠要比完成的牙冠大&#xff08;&#xff09; A.10% B.10%-15% C.15%-20% D.20%-30% E.50% 参考答案如图所示

微软AD域替代方案,助力企业摆脱hw期间被攻击的窘境

在红蓝攻防演练&#xff08;hw行动&#xff09;中&#xff0c;AD域若被攻击成功&#xff0c;是其中一个扣分最多的一项内容。每年&#xff0c;宁盾都会接到大量AD在hw期间被攻击&#xff0c;甚至是被打穿的企业客户。过去&#xff0c;企业还会借助2FA双因子认证加强OA、Exchang…

一个Vivado仿真问题的debug

我最近在看Synopsys的MPHY仿真代码&#xff0c;想以此为参考写个能实现PWM-G1功能的MPHY&#xff0c;并应用于ProFPGA原型验证平台。我从中抽取了一部分代码&#xff0c;用Vivado自带的仿真器进行仿真&#xff0c;然后就遇到了一个莫名其妙的问题&#xff0c;谨以此文作为debug…

Linux 本地RStudio 工具安装远程访问

前言 RStudio Server 使你能够在 Linux 服务器上运行你所熟悉和喜爱的 RStudio IDE&#xff0c;并通过 Web 浏览器进行访问&#xff0c;从而将 RStudio IDE 的强大功能和工作效率带到基于服务器的集中式环境中。 下面介绍在Linux docker中安装RStudio Server并结合cpolar内网…

力扣231. 2 的幂(数学,二分查找,位运算)

Problem: 231. 2 的幂 文章目录 题目描述思路即解法复杂度Code 题目描述 思路即解法 思路1&#xff1a;位运算 1.易验证2的幂为正数&#xff1b; 2.易得2的幂用二进制表示只能有一个位为数字1 3.即将其转换为二进制统计其二进制1的个数 思路2&#xff1a;数学 当给定数n大于1时…

C语言操作符超详细总结

文章目录 1. 操作符的分类2. 二进制和进制转换2.1 2进制转10进制2.1.1 10进制转2进制数字 2.2 2进制转8进制和16进制2.2.1 2进制转8进制2.2.2 2进制转16进制 3. 原码、反码、补码4.移位操作符4.1 左移操作符4.2 右移操作符 5. 位操作符&#xff1a;&、|、^、~6. 逗号表达式…

LabVIEW多任务实时测控系统

LabVIEW多任务实时测控系统 面对现代化工业生产的复杂性和多变性&#xff0c;传统的测控系统已难以满足高效、精准、可靠的监控和控制需求。因此&#xff0c;开发一种基于LabVIEW的智能测控系统&#xff0c;能够提高生产效率&#xff0c;保证生产安全&#xff0c;是解决现代工…

12 ABC串口接收原理与思路

1. 串口接收原理 基本原理&#xff1a;通过数据起始位判断要是否要开始接收的数据&#xff0c;通过采样的方式确定每一位数据是0还是1。 如何判断数据起始位到来&#xff1a;通过边沿检测电路检测起始信号的下降沿 如何采样&#xff1a;一位数据采多次&#xff0c;统计得到高…

SpringBoot和SpringMVC

目录 一、springboot项目 &#xff08;1&#xff09;创建springboot项目 &#xff08;2&#xff09;目录介绍 &#xff08;3&#xff09;项目启动 &#xff08;4&#xff09;运行一个程序 &#xff08;5&#xff09;通过其他方式创建和运行springboot项目 二、SpringMVC…

掌握虚拟化与网络配置之道:深入浅出VMware及远程管理技巧

目录 虚拟机介绍 虚拟机的关键字 服务器架构的发展 为什么用虚拟机VMware 虚拟机和阿里云的区别 功能角度 价格因素 应用场景 优势方面 找到windows的服务管理 配置VMware 关于VMware安装的几个服务 vmware如何修改各种网络配置 关于NAT的详细信息(了解) NAT(网…

2024年低压电工证模拟考试题库及低压电工理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年低压电工证模拟考试题库及低压电工理论考试试题是由安全生产模拟考试一点通提供&#xff0c;低压电工证模拟考试题库是根据低压电工最新版教材&#xff0c;低压电工大纲整理而成&#xff08;含2024年低压电工证…

嵌入式单片机中晶振的工作原理

晶振在单片机中是必不可少的元器件&#xff0c;只要用到CPU的地方就必定有晶振的存在&#xff0c;那么晶振是如何工作的呢&#xff1f; 什么是晶振 晶振一般指晶体振荡器&#xff0c;晶体振荡器是指从一块石英晶体上按一定方位角切下的薄片&#xff0c;简称为晶片。 石英晶体谐…

Three.js学习8:基础贴图

一、贴图 贴图&#xff08;Texture Mapping&#xff09;&#xff0c;也翻译为纹理映射&#xff0c;“贴图”这个翻译更直观。 贴图&#xff0c;就是把图片贴在 3D 物体材质的表面&#xff0c;让它具有一定的纹理&#xff0c;来为 3D 物体添加细节的一种方法。这使我们能够添加…