当前位置: 首页 > news >正文

Java:动态代理

Java:动态代理

在这里插入图片描述

什么是代理

代理模式 是一种设计模式,它为其他对象提供了一种代理以控制对这个对象的访问。代理对象通常包装实际的目标对象,以提供一些附加的功能(如延迟加载、访问控制、日志记录等)。我们一般可以使用装饰器模式来包装实际对象,从而实现代理模式,比如说:

//具体提供的服务接口
interface HelloService {void sayHello();
}
//服务的具体实现类
class ServiceImpl implements HelloService {@Overridepublic void sayHello() {System.out.println("Hello World!");}
}
//具体实现类的代理类--用来控制具体的服务访问和资源回收,日志打印等增强功能
class ServiceProxy implements HelloService{private HelloService target;ServiceProxy(HelloService target) {this.target = target;}@Overridepublic void sayHello() {try {long currentTimes = System.currentTimeMillis();System.out.println("Invoke Time is:" + currentTimes);target.sayHello();Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();} finally {recycleRes();}}//回收资源的方法private void recycleRes()  {System.out.println("回收Over");}
}

代理模式的优点

  • 职责清晰:
    代理模式将真实对象的实现与代理对象的控制逻辑分开,使得每个对象都承担单一职责,符合单一职责原则。
  • 控制访问:
    代理可以控制对目标对象的访问,这在需要控制权限或在访问前后添加额外操作时非常有用。例如,在远程代理中,可以控制客户端与服务器之间的通信。
  • 增强功能:
    不修改目标对象的情况下,代理模式可以在目标对象的访问前后添加额外的逻辑。例如,缓存代理可以缓存对象的返回结果以减少重复计算;日志代理可以记录方法的调用。
  • 延迟实例化:
    虚拟代理可以在真正需要目标对象时才创建它,从而节省内存和性能。例如,在图形应用程序中,如果图像对象较大,可以在首次需要显示时才进行加载。
  • 灵活性和可扩展性:
    代理模式提供了一种灵活的方式来扩展对象的功能。通过使用不同类型的代理,开发者可以轻松切换或扩展目标对象的行为。
  • 保护目标对象:
    保护代理可以控制对目标对象的访问权限,防止不适当的操作。这在多用户环境中尤其有用。

动态代理

代理的类型具体又可以分为静态代理动态代理,所谓静态代理,意思就是代理对象是在编译期就已经生成了,无法变更。而动态代理的意思就是代理对象是在运行期动态生成的。

在Java中,反射模块里提供了一个接口InvocationHandler来帮助我们实现动态代理,一些知名的开源库,比如说Retrofit,也是通过动态代理来实现具体的方法调用的:

public <T> T create(final Class<T> service) {validateServiceInterface(service);return (T)Proxy.newProxyInstance(service.getClassLoader(),new Class<?>[] {service},new InvocationHandler() {private final Platform platform = Platform.get();private final Object[] emptyArgs = new Object[0];@Overridepublic @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)throws Throwable {// If the method is a method from Object then defer to normal invocation.if (method.getDeclaringClass() == Object.class) {//如果外部调用的是 Object 中声明的方法的话则直接调用//例如 toString()、hashCode() 等方法return method.invoke(this, args);}args = args != null ? args : emptyArgs;//根据 method 是否默认方法来决定如何调用return platform.isDefaultMethod(method)? platform.invokeDefaultMethod(method, service, proxy, args): loadServiceMethod(method).invoke(args);}});}

我们也可以改造之前的例子实现一个动态代理的例子:

public static void main(String[] args) {HelloService service = new ServiceImpl();//通过Proxy.newProxyInstance方法生成具体的动态代理对象HelloService proxy = (HelloService) Proxy.newProxyInstance(//被代理的接口的类加载器service.getClass().getClassLoader(),//被代理的接口类型service.getClass().getInterfaces(),//具体实现了InvocationHandler接口的动态代理类new DynamicServiceProxy(service));//通过动态代理对象访问proxy.sayHello();}
}interface HelloService {void sayHello();
}class ServiceImpl implements HelloService {@Overridepublic void sayHello() {System.out.println("Hello World!");}
}class DynamicServiceProxy implements InvocationHandler {private HelloService target;public static String resource1 = "资源1";public static String resource2 = "资源2";public DynamicServiceProxy(HelloService target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("拦截方法:"+method.getName());System.out.println("执行时间:" + System.currentTimeMillis());String res = (String) getRes();Object resp = null;if (res.equals(resource1)) {resp = method.invoke(target,args);} else {System.out.println(resource2+"不可用!!!");}System.out.println("获取资源:"+res);System.out.println("执行完毕");return resp;}public Object getRes() {Random random = new Random();int ran = random.nextInt(10);return ran <= 5 ? (Object) resource1 : (Object) resource2;}
}

我们把 getRes() 作为一个模拟线上获取资源的方法,当获取到资源一时执行被代理类的原有逻辑,当获取到资源二时,我们就完全拦截原有的逻辑,而去执行我们自己的逻辑。这样就相当于是可以动态的选择方法的实际执行逻辑

使用场景

这种场景在直觉上显然就很适合鉴权访问的场景,先在先上验证当前用户是否有相应的权限,如果确定有相应权限在执行访问的逻辑,反之则拦截并提示无权限。

我们先用静态代理代理的方法实现需求:

interface ConnectionInterface {public String getResource(String Id);
}//静态代理管理访问权限
class ConnectionProxy implements ConnectionInterface {protected static Set<String> whiteList = Set.of("Android","IOS","Web","Server");private ConnectionInterface target;public ConnectionProxy(ConnectionInterface target) {this.target = target;}@Overridepublic String getResource(String Id) {if (!whiteList.contains(Id)) {System.out.println("没有权限!");return "Error";};return target.getResource(Id);}
}class ConnectionService implements ConnectionInterface {static Random random = new Random();static Map<String,Integer> resourceMap = Map.of("Android",random.nextInt(),"IOS", random.nextInt(),"Web",random.nextInt(),"Server", random.nextInt());@Overridepublic String getResource(String Id) {return resourceMap.get(Id).toString();}
}

如果我们后续有一个新的接口,或者说接口升级的话,我们还需要为这个新接口新实现一个代理类,而用动态代理就可以用一个动态代理类管理这两个逻辑:

interface ConnectionInterface {public String getResource(String Id);
}interface ConnectionInterfaceV2 {public String getResourceV2(String Id);
}//静态代理管理访问权限
class ConnectionProxy implements ConnectionInterface {protected static Set<String> whiteList = Set.of("Android","IOS","Web","Server");private ConnectionInterface target;public ConnectionProxy(ConnectionInterface target) {this.target = target;}@Overridepublic String getResource(String Id) {if (!whiteList.contains(Id)) {System.out.println("没有权限!");return "Error";};return target.getResource(Id);}
}class ConnectionProxyV2 implements ConnectionInterfaceV2 {protected static Set<String> whiteList = Set.of("Android","IOS","Web","Server");private ConnectionInterface target;public ConnectionProxyV2(ConnectionInterface target) {this.target = target;}@Overridepublic String getResourceV2(String Id) {System.out.println("新逻辑V2");if (!whiteList.contains(Id)) {System.out.println("没有权限!");return "Error";}return target.getResource(Id);}
}class Dynamic_Proxy implements InvocationHandler {protected static Set<String> whiteList = Set.of("Android","IOS","Web","Server");private Object target;public Dynamic_Proxy(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("拦截方法:"+method.getName());String param = (String) args[0];if (target instanceof ConnectionInterface) {if (whiteList.contains(param)) {System.out.println("有权限 V1");return method.invoke(target,param);} else {System.out.println("无权限 V1");return (Object) "Error";}}if (target instanceof ConnectionInterfaceV2) {if (whiteList.contains(param)) {System.out.println("有权限v2");return method.invoke(target,param);}System.out.println("无权限v2");}return (Object) "Default";}
}class ConnectionService implements ConnectionInterface {static Random random = new Random();static Map<String,Integer> resourceMap = Map.of("Android",random.nextInt(),"IOS", random.nextInt(),"Web",random.nextInt(),"Server", random.nextInt());@Overridepublic String getResource(String Id) {return resourceMap.get(Id).toString();}
}

这样我们相当于是减少了无用的代码量,实现了代码的逻辑复用。调用时我们可以这样使用:

public class DynamicPro {public static void main(String[] args) {ConnectionInterface service = new ConnectionProxy(new ConnectionService());ConnectionInterfaceV2 service2 = new ConnectionProxyV2(new ConnectionService());ConnectionInterface v1 = (ConnectionInterface)Proxy.newProxyInstance(service.getClass().getClassLoader(),service.getClass().getInterfaces(),new Dynamic_Proxy((Object) service));v1.getResource("Windows");ConnectionInterfaceV2 v2 = (ConnectionInterfaceV2) Proxy.newProxyInstance(service2.getClass().getClassLoader(),service2.getClass().getInterfaces(),new Dynamic_Proxy((Object) service2));v2.getResourceV2("Windows");}
}

这样我们轻松用一个代理类代理了两个对象。

具体的原理

这种在程序运行时动态修改方法入口的效果具体是基于Java的动态分派机制来实现的,即一个对象的方法调用总是在被调用时才真正确定其方法入口,对应到一个对象中,每个对象都有其的一个虚方法表,每次调用的时候就从这个虚方法表中查找具体的方法入口。

第二个机制就是基于Java中自己提供的反射框架,即在运行时可以动态生成方法对象,即Method对象,然后用Method对象作为逻辑,被代理类作为对象来执行。

使用 Proxy 类生成代理对象,InvocationHandler 接口来处理方法调用,是实现 AOP(面向切面编程)和其他动态功能的核心技术。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • CSP-CCF★★★201812-2小明放学★★★
  • Unity 热更 之 【YooAsset 热更】Unity 可以进行热更的资源管理系统,并 【Android 端简单实现·案例热更】
  • ABAP JSON处理应用
  • CKAD-CronJob
  • 伟易特发布全新一代便携式反无人机装备
  • Vue组件:动态组件、缓存组件、异步组件
  • CentOs7 解决yum更新源报错:[Errno 14] HTTP Error 404 - Not Found 正在尝试其它镜像。
  • 微信小程序登录与获取手机号 (Python)
  • 计算机毕业设计Spark+PyTorch知识图谱中药推荐系统 中药数据分析可视化大屏 中药爬虫 机器学习 中药预测系统 中药情感分析 大数据毕业设计
  • opencv学习:信用卡卡号识别
  • 别总是“系统错误,请稍后重试!”了,解决问题要彻底!
  • 铲屎官都该知道的除浮毛神器——希喂、美的、352宠物空气净化器
  • Python VTK 绘制等高线初步
  • image.size()和image.shape包含的信息一样,那image.size()存在的意义是什么?
  • 2024.9.9(极客大挑战 2019]EasySQL,[极客大挑战 2019]Knife)
  • 【Leetcode】101. 对称二叉树
  • 收藏网友的 源程序下载网
  • [数据结构]链表的实现在PHP中
  • 2018一半小结一波
  • css系列之关于字体的事
  • Django 博客开发教程 8 - 博客文章详情页
  • es6要点
  • Java 23种设计模式 之单例模式 7种实现方式
  • Mac转Windows的拯救指南
  • Python_网络编程
  • Redis在Web项目中的应用与实践
  • use Google search engine
  • Webpack4 学习笔记 - 01:webpack的安装和简单配置
  • webpack项目中使用grunt监听文件变动自动打包编译
  • 安卓应用性能调试和优化经验分享
  • 大快搜索数据爬虫技术实例安装教学篇
  • 分布式任务队列Celery
  • 利用DataURL技术在网页上显示图片
  • 面试总结JavaScript篇
  • 那些年我们用过的显示性能指标
  • 在Docker Swarm上部署Apache Storm:第1部分
  • 如何在 Intellij IDEA 更高效地将应用部署到容器服务 Kubernetes ...
  • # 达梦数据库知识点
  • # 利刃出鞘_Tomcat 核心原理解析(七)
  • # 详解 JS 中的事件循环、宏/微任务、Primise对象、定时器函数,以及其在工作中的应用和注意事项
  • #include到底该写在哪
  • #宝哥教你#查看jquery绑定的事件函数
  • (Matalb回归预测)PSO-BP粒子群算法优化BP神经网络的多维回归预测
  • (附源码)springboot工单管理系统 毕业设计 964158
  • (附源码)springboot课程在线考试系统 毕业设计 655127
  • (七)c52学习之旅-中断
  • (强烈推荐)移动端音视频从零到上手(上)
  • (十一)图像的罗伯特梯度锐化
  • (五)大数据实战——使用模板虚拟机实现hadoop集群虚拟机克隆及网络相关配置
  • (一)Mocha源码阅读: 项目结构及命令行启动
  • (转载)Linux 多线程条件变量同步
  • .NET Core 和 .NET Framework 中的 MEF2
  • .NET DevOps 接入指南 | 1. GitLab 安装
  • .NetCore+vue3上传图片 Multipart body length limit 16384 exceeded.
  • .Net环境下的缓存技术介绍