代理模式以及静态代理、JDK代理、Cglib代理的实现

代理模式(Proxy)

介绍

1、代理模式:为对象提供一个替身,以控制对这个对象的访问,即通过代理对象访问目标对象,这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作

(即:扩展目标对象的功能,例如Spring AOP)

2、被代理的对象可以是远程对象,创建开销大的对象或需要安全控制的对象

3、代理模式有不同的形式,主要有三种:

  • 静态代理(也是一种基于接口的代理)

  • 动态代理(JDK代理/接口代理)

  • Cglib代理(在内存中动态创建对象,而不需要实现接口,属于动态代理的范畴)

这里是代理的示意图:

image-20240308112221180

静态代理

静态代理在使用时,需要定义接口或父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同的接口

代理对象与目标对象实现相同的接口,通过调用相同的方法来调用目标对象方法。

代码示例

// 定义接口
interface Subject {void doAction();
}
​
// 目标对象
class RealSubject implements Subject {@Overridepublic void doAction() {System.out.println("RealSubject is doing action");}
}
​
// 代理对象
class ProxySubject implements Subject {
//这里聚合一个RealSubject对象private RealSubject realSubject;
​public ProxySubject(RealSubject realSubject) {this.realSubject = realSubject;}
​@Overridepublic void doAction() {System.out.println("ProxySubject is doing something before action");realSubject.doAction();System.out.println("ProxySubject is doing something after action");}
}
​
// 测试类
public class StaticProxyExample {public static void main(String[] args) {RealSubject realSubject = new RealSubject();ProxySubject proxySubject = new ProxySubject(realSubject);
​// 通过代理对象调用目标对象的方法proxySubject.doAction();}
}

image-20240308113224968

小结

优点:

  1. 易于理解和实现: 静态代理模式相对简单,易于理解和实现,适用于一些简单的代理场景。

  2. 对目标对象进行控制: 通过代理可以对目标对象的访问进行控制和管理,例如在调用目标对象方法前后进行额外的处理。

  3. 实现业务功能的解耦: 代理对象可以在不修改目标对象的情况下,增加额外的功能,实现了业务功能的解耦,提高了系统的可维护性。

  4. 保护目标对象: 代理对象可以充当一个保护层,控制用户对目标对象的访问权限,增加系统的安全性。

缺点:

  1. 静态: 静态代理在编译时就已经确定了代理类和目标类,如果需要代理多个类或者频繁地更换代理对象,会导致代码量增加,不利于系统的扩展和维护。

  2. 灵活性差: 静态代理模式的灵活性较低,一旦接口或者目标对象发生变化,代理类也需要相应地修改,不够灵活。

  3. 代码重复: 如果有多个接口或者多个目标对象需要被代理,会导致代理类的重复设计和编码,增加了开发工作量。

  4. 只能代理固定的目标对象: 静态代理模式在编译时就已经确定了代理类和目标类的关系,无法动态地切换或者增加新的代理对象。

总的来说,静态代理模式在一些简单的场景下是适用的,但在面对复杂的系统、多样的需求和频繁变化的代理对象时,可能会显得不够灵活和可扩展。在这种情况下,可以考虑使用动态代理模式或者其他结构型模式来实现更灵活、可扩展的代理功能。

动态代理

介绍

1、代理对象不需要实现接口,但是目标对象要实现接口,否则不能用动态代理

2、代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象

3、动态代理又称:JDK代理,接口代理

JDK中生成代理对象的API

1、代理类所在包:java.lang.reflect.Proxy

2、JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收3个参数,完整写法是:

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

代码示例

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
​
// 定义接口
interface Subject {void doAction();
}
​
// 目标对象
class RealSubject implements Subject {@Overridepublic void doAction() {System.out.println("目标对象正在执行");}
}
​
// InvocationHandler 实现类
class ProxyHandler implements InvocationHandler {private Object target;
​public ProxyHandler(Object target) {this.target = target;}
​@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("目标对象执行前。。。");Object result = method.invoke(target, args);System.out.println("目标对象执行后。。。");return result;}
}
​
// 测试类
public class DynamicProxyExample {public static void main(String[] args) {RealSubject realSubject = new RealSubject();
​// 创建动态代理对象//newProxySubject的三个参数://1、ClassLoader:指定当前目标对象使用的类加载器,获取加载器的方法固定//2、Class<?>[] interfaces:目标对象实现的接口类型,使用泛型方法确定类型//3、InvocationHandler h:事情处理。执行目标对象方法时,会触发事情处理方法,会把当前执行的目标对象方法作为参数Subject proxySubject = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),new Class[]{Subject.class},new ProxyHandler(realSubject));
​// 通过代理对象调用目标对象的方法proxySubject.doAction();}
}
​

image-20240308130544273

小结

优点:

  1. 灵活性: 动态代理模式相比静态代理模式更加灵活,可以在运行时动态地创建代理对象,无需提前知道目标对象的具体类型。

  2. 减少代码量: 动态代理可以通过反射机制自动生成代理类,减少了手动编写代理类的代码量。

  3. 适应性强: 动态代理模式适用于多个目标类共享一个代理类或者频繁切换代理对象的场景。

  4. 可扩展性: 动态代理模式可以通过自定义 InvocationHandler 的实现类,实现对目标对象方法的统一处理,并且可以根据需求灵活地扩展功能。

缺点:

  1. 性能影响: 动态代理在运行时通过反射机制调用目标对象的方法,相比直接调用目标对象的方法,性能上会有一定的影响。

  2. 复杂性增加: 动态代理模式相对静态代理模式来说,涉及到更多的类和概念,代码结构可能会变得更加复杂,不利于代码的理解和维护。

  3. 无法代理私有方法: 动态代理无法直接代理目标对象中的私有方法,只能代理公共方法。

动态代理模式在一些需要灵活性和可扩展性的场景下非常有用。它可以减少代码量、提高代码的可复用性,并且可以在运行时动态地创建代理对象。然而,它也存在一些性能影响和复杂性增加的问题,需要根据具体情况权衡使用。

Cglib代理

介绍

1、静态代理和JDK代理都要求目标对象实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理——Cglib代理

2、Cglib代理也叫做子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些地方也将Cglib代理归属动态代理

3、Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。

4、Cglib代理广泛使用在AOP框架中,例如Spring AOP,实现方法拦截。

在AOP编程中,如何选择代理模式呢?

  • 目标对象需要实现接口——JDK代理

  • 目标对象不需要实现接口——Cglib代理

5、Cglib包底层是通过使用字节码处理框架ASM来转换字节码并生成新的类

实现步骤

  1. 引入cglib的jar文件

     <dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.4.1</version></dependency>
  2. 因为在内存中动态构建子类,代理类不能是final,否则会报错

  3. 目标对象的方法如果是final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
​
import java.lang.reflect.Method;
​
/*** cglib代理测试*/
​
class HelloWorld {
​public void sayHello() {System.out.println("CGLIB动态代理模式!");}
​
}
class CGLIBExampleProxy implements MethodInterceptor {
​/*** 指定cglib代理模式的代理类*/
​private Object target;
​public Object bind(Object target) {this.target = target;Enhancer enhancer = new Enhancer();//设置超类方法enhancer.setSuperclass(this.target.getClass());//设置一个回调方法,用来设置哪个类为代理类,this表示当前类为代理类enhancer.setCallback(this);//创建代理对象return enhancer.create();
​}
​//重写接口中的intercept方法,增强并执行代理方法public Object intercept(Object obj,Method method, Object[] args,MethodProxy proxy) throws Throwable {System.out.println("CGLIB代理前");Object object = proxy.invokeSuper(obj, args);System.out.println("CGLIB代理后");return object;}
}
public class CglibProxy {public static void main(String[] args) {//1.获取代理对象CGLIBExampleProxy proxy=new CGLIBExampleProxy();//2.获取代理对象HelloWorld hello=(HelloWorld)proxy.bind(new HelloWorld());//3.执行代理方法hello.sayHello();}
}

image-20240308135222605

如果报错

原因:
因为在使用 CGLIB 进行代理时,出现了对 Java 9+ 模块系统的限制导致的异常。在 Java 9+ 中,模块系统对于反射和类加载引入了更严格的访问控制。
​
解决:
尝试通过添加以下 JVM 参数来打开 java.base 模块的反射权限:
--add-opens java.base/java.lang=ALL-UNNAMED

image-20240308164455740

image-20240308164518694

image-20240308164556312

image-20240308164646127

image-20240308164744115

小结

优点:

  1. 不需要接口: CGLib 动态代理不要求目标对象实现接口,可以直接代理目标类的方法,这样就避免了 JDK 动态代理必须基于接口的限制。

  2. 性能高: CGLib 动态代理是通过生成目标类的子类来实现代理,因此在调用目标方法时比 JDK 动态代理更快,无需通过反射调用。

  3. 灵活性: CGLib 可以代理非公共的类,包括 final 类。同时,CGLib 也支持对类中的 final 方法进行代理。

  4. 功能丰富: CGLib 提供了丰富的 API,可以在代理过程中实现更复杂的逻辑,满足更多场景的需求。

缺点:

  1. 引入依赖: 使用 CGLib 动态代理需要引入额外的库,增加了项目的依赖性,可能会对项目构建和部署造成一定影响。

  2. 类加载器: 由于 CGLib 是通过生成目标类的子类来实现代理,因此需要使用字节码技术,在某些情况下可能会导致类加载问题或者冲突。

  3. 无法代理静态方法: CGLib 无法代理目标对象中的静态方法,因为动态代理实际上是通过生成目标对象的子类来实现代理,而静态方法是属于类级别的而不是实例级别的。

  4. 内存占用: 由于 CGLib 动态代理是通过生成子类来实现代理,可能会占用更多的内存,特别是在代理大量对象时可能会影响系统性能。

总的来说,CGLib 动态代理适合对类进行代理,无需目标对象实现接口的情况下使用。它具有高性能、灵活性强等优点,但也存在一些缺点,如引入依赖、无法代理静态方法等。在选择动态代理方式时,需要根据具体需求和场景来权衡选择适合的代理方式。

几种常见的代理模式介绍——几种变体

1、防火墙代理

内网通过代理穿透防火墙,实现对公网的访问

2、缓存代理

比如:当请求图片文件等资源时,先到缓存代理取,如果取不到,就到公网或数据库中取

3、远程代理 把远程对象当本地对象来调用。远程代理通过网络和真正的远程对象沟通信息

4、同步代理

用于多线程编程中,完成多线程间同步工作。

通过一个代理的服务器去访问真正提供服务的。

代理模式总结

本人理解:通过一个代理对象访问并增强另一个对象的功能。

代理模式是一种结构型设计模式,其目的是通过引入一个代理对象来控制对另一个对象的访问。代理对象可以在不改变原始对象的情况下,对其进行一些额外的操作,如控制对原始对象的访问权限、延迟加载、缓存数据、记录日志等。

角色

  • 抽象主题(Subject):定义了代理对象和真实对象的共同接口,客户端可以通过该接口访问真实对象。

  • 真实主题(Real Subject):实际执行业务逻辑的对象。

  • 代理(Proxy):包含了对真实主题的引用,并提供与真实主题相同的接口,负责控制对真实主题的访问。

优势

  • 代理对象可以拦截对真实对象的访问,可以在调用真实对象之前或之后执行一些额外的逻辑。

  • 可以实现延迟加载:代理对象可以延迟创建或加载真实对象,从而提高系统性能和节省资源。

  • 实现访问控制:代理对象可以控制对真实对象的访问权限,实现安全性控制。

  • 实现缓存:代理对象可以缓存真实对象的结果,避免重复计算或获取数据。

缺点

  • 增加系统复杂度

  • 性能损耗

  • 可能引入单点故障

  • 可能导致循环依赖

应用场景

  • 远程代理:控制访问远程对象。

  • 虚拟代理:控制访问创建开销大的对象。

  • 缓存代理:为耗时的操作结果提供缓存。

  • 保护代理:控制对敏感对象的访问权限。

  • 智能引用:在访问对象时执行额外操作,如引用计数。

代理模式能够增加代码的灵活性和可维护性,同时提供了一种优雅的方式来控制对象的访问和行为。通过合理地使用代理模式,可以更好地实现代码解耦和功能扩展。

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

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

相关文章

java-ssm-jsp-大学生互动交流网站设计与实现

java-ssm-jsp-大学生互动交流网站设计与实现 获取源码——》公主号&#xff1a;计算机专业毕设大全

Linux运维:实现光盘开机自动挂载、配置本地yum源教程

Linux运维&#xff1a;实现光盘开机自动挂载、配置本地yum源教程 一、光盘开机自动挂载1、检查光驱设备2、创建挂载点3、编辑/etc/fstab文件4、测试挂载 二、配置本地yum源(挂载光盘或ISO文件)1、挂载ISO文件2、创建YUM仓库配置文件3、清理YUM缓存并测试 &#x1f496;The Begi…

数据结构——lesson7二叉树 堆的介绍与实现

前言&#x1f49e;&#x1f49e; 啦啦啦~这里是土土数据结构学习笔记&#x1f973;&#x1f973; &#x1f4a5;个人主页&#xff1a;大耳朵土土垚的博客 &#x1f4a5; 所属专栏&#xff1a;数据结构学习笔记 &#x1f4a5;对于数据结构顺序表链表有疑问的都可以在上面数据结…

python的虚拟环境

python的虚拟环境可以为项目创建一个独立的环境&#xff0c;能够解决使用不同版本依赖给项目带来冲突的麻烦。创建虚拟环境的方式有很多种&#xff0c;pipenv会自动帮你管理虚拟环境和依赖文件&#xff0c;并且提供了一系列命令和选项来帮忙你实现各种依赖和环境管理相关的操作…

代码随想录算法训练营第三十九天|62.不同路径、63. 不同路径 II

62.不同路径 刷题https://leetcode.cn/problems/unique-paths/description/文章讲解https://programmercarl.com/0062.%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84.html视频讲解https://www.bilibili.com/video/BV1ve4y1x7Eu/?vd_sourceaf4853e80f89e28094a5fe1e220d9062 题解&…

储能系统---交流充电桩(三)

一、充电模式及其功能要求 关注公众号 --- 小Q下午茶 新国标在标准 GB/T 18487.1-2015《电动汽车传导充电系统 第1部分&#xff1a;通用要求》中规定了 4 种充电模式&#xff0c;下面将对这 4 种充电模式及其功能要求进行介绍。 1.1 、模式 1 模式 1 是指在充电系统中应使用…

3D数字孪生运行不起来?该检查你的电脑配置了

运行3D数字孪生项目通常需要一定的计算资源和图形处理能力。以下是一些常见的电脑配置要求&#xff0c;可以作为参考&#xff1a;1处理器&#xff08;CPU&#xff09;&#xff1a;推荐使用多核心处理器&#xff0c;如Intel Core i7或更高级别的处理器。较高的时钟频率和较大的缓…

使用 Docker 部署 File Browser 文件管理系统

1&#xff09;File Browser 介绍 官网&#xff1a;https://filebrowser.org/ GitHub&#xff1a;https://github.com/filebrowser/filebrowser 今天为大家分享一款开源的私有云盘项目&#xff1a;File Browser&#xff0c;简单实用、轻量级、跨平台&#xff0c;安装部署简单快…

CMake笔记

CMake笔记 文章目录 CMake笔记1 工程项目一般形式2 常见命令2.1 project2.2 set2.3 message2.4 add_executable()2.5 语法原则2.6 add_subdirectory2.7 add_library2.8 list 3 安装3.1 安装.h文件/文本文件3.2 安装工程脚本3.3 安装目录/目录下内容3.4 安装库文件3.5安装过程 4…

AI大模型,掀起新一波智能浪潮!

AI大模型的出现&#xff0c;标志着人工智能技术迈入了一个新的阶段。这些巨大的模型不仅在规模上超越了以往任何其他人工智能系统&#xff0c;而且在性能上也取得了巨大的突破。由于其庞大的参数量和复杂的结构&#xff0c;AI大模型在各个领域展现出了强大的学习能力和推理能力…

434G数据失窃!亚信安全发布《勒索家族和勒索事件监控报告》

最新态势快速感知 最新一周全球共监测到勒索事件90起&#xff0c;与上周相比数量有所增加。 lockbit3.0仍然是影响最严重的勒索家族&#xff1b;alphv和cactus恶意家族也是两个活动频繁的恶意家族&#xff0c;需要注意防范。 Change Healthcare - Optum - UnitedHealth遭受了…

一条SQL引起的系统不可用

一.前言 最近在运维系统&#xff0c;系统对客端突然报了403错误&#xff0c;从后台看发现了大量的慢SQL&#xff0c;导致查询超时&#xff0c;仔细分析我从来没见过那么厚颜无耻的SQL&#xff0c;一条SQL语句关联了一个大表&#xff08;6000数据&#xff09;查询了10次。我也很…

2024年第二届智能制造与自动化前沿国际会议 | Ei、Scopus双检索

会议简介 Brief Introduction 2024年第二届智能制造与自动化前沿国际会议&#xff08;CFIMA 2024&#xff09; 会议时间&#xff1a;2024年8月23 -25日 召开地点&#xff1a;中国包头 大会官网&#xff1a;www.cfima.org 随着全球新一轮科技革命和产业变革突飞猛进&#xff0c;…

LeetCode 刷题 [C++] 第3题.无重复字符的最长子串

题目描述 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 题目分析 可以使用滑动窗口加哈希表来实现&#xff1a; 使用start和end两个变脸来表示滑动窗口的头部位置和尾部位置&#xff0c;两者开始均为0&#xff1b;借助哈希表来记录已经遍…

JVM(类加载机制)

类加载就是 .class 文件, 从文件(硬盘) 被加载到内存(元数据区)中的过程 类加载的过程 加载: 找 .class 文件的过程, 打开文件, 读文件, 把文件读到内存中 验证: 检查 .class 文件的格式是否正确 .class 是一个二进制文件, 其格式有严格的说明 准备: 给类对象分配内存空间 (先在…

专业140+总430+电子科技大学858信号与系统考研经验成电电子信息与通信工程,电科大,真题,大纲,参考书。

今年考研成绩出来&#xff0c;初试专业课858信号与系统140&#xff0c;总分430&#xff0c;其余各门分数都比较平稳&#xff0c;总分好于自己估分&#xff0c;应群里很多同学要求&#xff0c;我总结一下自己的复习经验。首先我是一个大冤种&#xff0c;专业课资料学长给了一套&…

uniapp实现---类似购物车全选

目录 一、实现思路 二、实现步骤 ①view部分展示 ②JavaScript 内容 ③css中样式展示 三、效果展示 四、小结 注意事项 一、实现思路 点击商家复选框&#xff0c;可选中当前商家下的所有商品。点击全选&#xff0c;选中全部商家的商品 添加单个多选框&#xff0c;在将多选…

react tab选项卡吸顶实现

react tab选项卡吸顶实现&#xff0c;直接上代码&#xff08;代码有注释&#xff09; tsx代码 /* eslint-disable react-hooks/exhaustive-deps */ import React, { useEffect, useState } from "react"; import DocumentTitle from react-document-title import s…

UE5中实现后处理深度描边

后处理深度描边可以通过取得边缘深度变化大的区域进行描边&#xff0c;一方面可以用来做角色的等距内描边&#xff0c;避免了菲尼尔边缘光不整齐的问题&#xff0c;另一方面可以结合场景扫描等特效使用&#xff0c;达到更丰富的效果&#xff1a; 后来解决了开启TAA十字线和锯齿…

Java零基础 - 数组的定义和声明

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一个人虽可以走的更快&#xff0c;但一群人可以走的更远。 我是一名后…