[程序设计]—代理模式

[程序设计]—代理模式👳

本文章记录学习于——52.面向切面:AOP-场景模拟_哔哩哔哩_bilibili

最近闲来无事,在学习Spring的源码: 后面慢慢更新源码系列blog,希望多多关注🙏🙏

目前已经总结的blog 系列🕳: 慈様や 前端学习导航👩🏻‍🚀🚀 、小様—Java后端开发日记

很多框架在设计和实现过程中广泛运用了多种设计模式:Spring核心IOC、AOP

  • 工厂模式: BeanFactoryApplicationContext 实现了工厂模式,负责创建和管理Bean对象

  • 单例模式: Spring 默认 将所有Bean定义为单例模式,即每个容器中只存在一个实例

  • 代理模式: AOP 面向切面编程的核心,通过动态代理:JDK代理CGLIB代理

    实现了非侵入式编程,可以在不修改原有业务代码的情况下增加额外功能

    在目标方法调用前后添加增强逻辑,如事务管理、日志记录等,

  • 适配器默认、观察者默认、策略模式… 设计模式使,

    Spring框架展现出了高度的模块化、可配置性和可扩展性,使得开发者能够高效地构建复杂的应用程序

👊 总而颜值->设计模式很重要,

设计模式其实是在软件开发过程中经过经验积累和验证总结,一套通用代码设计方案

如果熟悉了设计模式,当遇到类似的场景,我们可以快速地参考设计模式实现代码,

不仅可以加速我们的编码速度,也提升了代码的:扩展性、重用性、维护性!

个人看法:

虽然,设计模式听着高大上,其实实际开发过程中已经潜移默化的接触了很多了,

且有的设计模式已经随着时代、或还并没有完全适合的引用常见,新手建议会用为主,了解为辅

设计模式:属于程序的内功心法💖、学习是一个长期反复推敲的过程、不断优化升级🆙🆙❗ ❗ ❗

  • 注意:设计模式不属于某个特定的编程语言,而是一种编程思想,代码模式;

设计模式分类:

目前设计模式主要有:23种,大致分为三大类:

创建型模式 Creational Patterns 用于创建对象的模式,同时隐藏对象创建的逻辑,

避免代码中出现大量 new 操作和复杂的创建逻辑,目的是解耦对象的创建和使用

  • 常用的有: 单例模式、工厂模式、建造者模式
  • 不常见的有: 原型模式

结构型模式 Structural Patterns

用于处理对象组合的结构,关注类与对象的组合,目的是通过组合对象或类的方式,形成更大的结构

  • 常用的有: 适配器模式、桥接模式、组合模式、装饰器模式、代理模式
  • 不常见的有: 外观模式(门面模式)、享元模式

行为型模式Behavioral Patterns

用于定义对象相互协作以完成单个对象无法单独实现的任务,目的:是定义类和对象间的通信方式;

  • 常用的有: 观察者模式、责任链模式、策略模式、模板模式、状态模式、迭代器模式,
  • 不常见的有: 备忘录模式、命令模式、中介者模式、访问者模式、解释器模式

需求介绍:

目前有一个计算器🧮类,它有add 加sub 减 的方法,新需求: 计算前后进行日志记录输出;

声明计算器接口: 用于规范计算器类的定义;

//计算器类接口:
public interface Calculator {
    //计算器加函数
    int add(int i,int j);

    //计算器减函数
    int sub(int i,int j);
}

计算器接口实现类: 声明并实现计算器接口;

//计算器接口实现类:
public class CalculatorImpl implements Calculator {
    @Override
    public int add(int i, int j) { return i+j; }

    @Override
    public int sub(int i, int j) { return i-j; }
}

新需求:在加减之前进行日志记录——计算器日志接口实现类:

  • 正常情况下,大部分开发者会直接在上面实现类上改动,但为了不影响其他就创建新的实现类;
//计算器日志接口实现类: 不就是日志嘛一行代码CV的事~~
public class CalculatorLogImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        System.out.println("[日志]mul 方法开始了,参数是:"+i+","+ j);
        System.out.println("[日志]mul 方法结束了,结果是:"+(i + j));
        return i + j;
    }

    @Override
    public int sub(int i, int j) {
        System.out.println("[日志]mul 方法开始了,参数是:"+i+","+ j);
        System.out.println("[日志]mul 方法结束了,结果是:"+(i - j));
        return i-j;
    }
}

在这里插入图片描述

轻轻松松~ 小活😀 这就结束了吗?到此为止了…

NO—NO—NO❗ ❗ ❗ 如果这个类在大一点呢?或者方法实现更复杂呢?

  • 针对日志功能实现类,我们发现有如下缺陷:对核心业务功能有干扰,附加功能分散各个业务功能中;

🆗,主角登场——代理模式:👇👇👇

JDK 代理模式

代理模式是一种结构型设计模式:

它为其他对象提供一种代理,以控制对这个对象的访问,代理对象可以在客户端和目标对象之间起到中介的作用,

在不改变目标对象接口的前提下,对目标对象的访问进行控制或增强,上述代码举例:👆👆👆

代理模式的特点:

  • 功能增强: 代理对象可以在访问实际对象之前或之后添加额外的功能
  • 间接访问: 客户端通过代理访问实际对象,代理对象负责对实际对象的控制
  • 代码解耦: 客户端不直接与实际对象交互,通过代理对象可以透明地扩展实际对象的功能

代理模式的分类:

  • 静态代理: 在编译时创建代理类,代理类与目标类实现相同的接口,
  • 动态代理: 在运行时动态生成代理类,适用于无需事先定义代理类的场景

静态代理:

静态代理:不推荐) 了解即可,实际开发中不经常使用,

代理类和被代理类在 编译时期 就确定了它们之间的代理关系,

代理类需要 实现 与被代理类 相同的接口 ,并且在代理类中持有被代理类的实例 目标对象

通过调用被代理类的方法,来完成实际的操作,在方法调用前后添加额外的逻辑; 这一点也 不高级;

//静态代理类
public class CalculatorStaticProxy implements Calculator {
    //被代理的目标对象
    private Calculator target;
    public CalculatorStaticProxy(Calculator target) { this.target = target; }


  	@Override
		//附加功能由代理类中的代理方法来实现
    public int add(int i, int j) {
        System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
      
        //通过目标对象来实现核心业务逻辑
        int addResult = target.add(i, j);
      
        System.out.println("[日志] add 方法结束了,结果是:" + addResult);
        return addResult;
    }
  //省略...其他实现;
}
//静态代理使用:
public class CalculatorStaticProxyMain {
    public static void main(String[] args) {
        //创建被代理类对象
        CalculatorImpl calculator = new CalculatorImpl();
        //被代理类对象 ==>构造创建 对应的 ==> 静态代理类,静态代理类中对函数进行调用扩展;
        CalculatorStaticProxy calStaticProxy = new CalculatorStaticProxy(calculator);
        //因此: 直接使用静态代理类对象即可,获得存在日志的计算机函数方法;
        calStaticProxy.add(1,1);
    }
}

在这里插入图片描述
静态代理确实实现了解耦:,但是由于代码都写死了,完全不具备任何的灵活性

  • 如果:将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码

如何: 将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现——动态代理技术

动态代理:

涉及知识: 【反射】、【类加载ClassLoad】… ,JDK 官方文档📑 Java.lang.reflect.Proxy

Java 动态代理是一种在运行时 动态地创建代理对象的机制:

  • 它允许开发者在不修改原有类代码的基础上,

  • 为目标对象创建代理,并且能够在代理对象的方法调用前后灵活地添加自定义的逻辑;

与静态代理不同: 动态代理不需要为每个目标类手动编写代理类,在JDK中,常用的实现方式是 反射

  • 反射机制: 是指程序在运行期间可以访问、检测和修改其本身状态或行为的一种能力

实现原理

实现原理: 基于 Java 的java.lang.reflect包:InvocationHandler接口

开发者需要实现这个接口,并且实现invoke方法,在invoke方法中定义代理对象的方法被调用时要执行逻辑;

  • invoke方法有三个参数: proxy代理对象本身、method被调用的方法对象、args方法的参数数组;

Proxy类: 通过Proxy.newProxyInstance 方法可以 创建生成 代理对象;这个方法需要三个参数:

  • 目标对象的类加载器:目标对象.getClass().getClassLoader(),
  • 目标对象的接口数组:目标对象.getClass().getInterfaces(),
  • InvocationHandler 接口实例;

🆗,到此就获得了一个:(动态)代理对象: 使用动态代理对象,具有目标对象,一样的方法([参数]);

  • 通过代理对象方法时,实际上会调用InvocationHandler实例对象invoke方法,方法中,执行了增强逻辑;

在这里插入图片描述

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

//动态代理类
public class DynamicProxy implements InvocationHandler {
    //目标代理对象
    private Object target;
    public DynamicProxy(Object target) { this.target = target; }
    @Override
    //proxy代理对象本身、method被调用的方法对象、args方法的参数数组
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("[日志] "+method.getName()+" 方法开始了,参数是:" + args);
        // 通过目标对象来实现核心业务逻辑
        Object addResult = method.invoke(target, args);
        System.out.println("[日志] "+method.getName()+" 方法结束了,结果是:" + addResult);
        return addResult;
    }
}
import org.proxyD.Calculator;
import java.lang.reflect.Proxy;
import org.proxyD.CalculatorImpl;

/** 动态代理使用: */
public class DynamicProxyMain {
    public static void main(String[] args) {
        //定义目标对象、InvocationHandler接口实例
        CalculatorImpl calculator = new CalculatorImpl();
        DynamicProxy calStaticProxy = new DynamicProxy(calculator);
        //Proxy.newProxyInstance(目标类加载对象,目标接口数组,InvocationHandler接口实例) 动态创建生成代理对象
        Calculator Calculatorimpl = (Calculator) Proxy.newProxyInstance(
                //calculator 目标对象通过接口getClass 获得类加载对象、接口数组
                calculator.getClass().getClassLoader(),
                calculator.getClass().getInterfaces(),
                //InvocationHandler 接口实例
                calStaticProxy
        );
        //使用: 生成代理类
        Calculatorimpl.add(1,1);
        Calculatorimpl.sub(1,1);
    }
}

在这里插入图片描述

动态代理优势: 可以在运行时根据需要动态地创建代理对象,而不需要像静态代理那样为每个目标类提前编写代理类

代码复用性强:一个InvocationHandler实现类可以用于代理多个实现相同接口的目标对象

易于维护和扩展:由于代理逻辑集中在InvocationHandler实现类的invoke方法中,

当需要修改代理逻辑时,只需要修改这个方法即可,而不需要多个代理类进行修改;

优化—代理工厂:

考虑到代码复用与管理: 可以对上述代码进行优化一个 代理工厂,Proxy.newProxyInstance 生成代理类操作进行统一管理;

匿名内部类实现: InvocationHandler接口,重写了invoke方法;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

//动态代理工厂
public class ProxyFactory {
    //目标对象
    private Object target;
    public ProxyFactory(Object target) { this.target = target; }

    //动态代理对象函数
    //通过: 创建 InvocationHandler接口 匿名实现类对象,直接返回 动态代理对象;
    public Object getProxy(){
				/** newProxyInstance():创建一个代理实例 其中有三个参数: */
        /** 1、classLoader:加载动态生成的代理类的类加载器 */
        ClassLoader classLoader = target.getClass().getClassLoader();
        /** 2、interfaces:目标对象实现的所有接口的class对象所组成的数组 */
        Class<?>[] interfaces = target.getClass().getInterfaces();
        /** 3、通过: 创建 InvocationHandler接口 匿名实现类对象,直接返回 动态代理对象
         * invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接口中的抽象方法 */
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try {
                    System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args));
                    result = method.invoke(target, args);
                    System.out.println("[动态代理][日志] "+method.getName()+",结果:"+ result);
                } catch (Exception e) {
                    e.printStackTrace();
                    System.out.println("[动态代理][日志] "+method.getName()+" 异常");
                } finally {
                    System.out.println("[动态代理][日志] 方法执行完毕");
                }
                return result;
            }
        };
        //最后返回代理对象;
        return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
    }
}

在这里插入图片描述

CGLIB 动态代理:

JDK动态代理是基于接口的,所以要求代理类一定是有定义接口的,

CGLIB 基于 ASM 字节码生成工具,它是通过 继承的方式生成目标类的子类来实现代理类

  • 注意 final 方法,不可继承

  • 它们之间的性能随着 JDK 版本的不同而不同:

  • JDK6 在运行次数较少的情况下,动态代理与 CGLIB 差距不明显,次数增加之后 CGLIB 更快

  • JDK7 情况发生了逆转!在运行次数较少1.000,000情况下,JDK 动态代理比 CGLIB 快了差不多30%;

    而当调用次数增加之后(50000000),JDK动态代理比 CGLIB 快了接近1倍,Jdk8 表现和 Jdk7 基本一致

CGLIB 的工作原理

继承目标类:CGLIB 通过 继承目标类 来创建代理类。在生成的代理类中,它会重写目标类的方法

方法拦截:CGLIB 利用方法拦截器(MethodInterceptor)来控制对目标类方法的访问;

  • 当代理类的方法被调用时,会先进入方法拦截器的intercept方法;进行操作;

添加 CGLIB 依赖:CGLIB 是第三方库,所以需要引入对应依赖;

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib-nodep</artifactId>
    <version>3.3.0</version>
</dependency>

创建方法拦截器:实现 CGLIB 的MethodInterceptor接口来定义方法拦截逻辑,重写:intercept方法:

  • 第一个参数o是代理对象本身

  • 第二个参数method是被调用的目标方法

  • 第三个参数objects是目标方法的参数数组

  • 第四个参数methodProxy是一个方法代理对象,用于调用目标类的原始方法

    通过methodProxy.invokeSuper(o, objects)可以调用目标类的原始方法,并获取返回结果;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
//实现 CGLIB 的MethodInterceptor接口来定义方法拦截逻辑
public class CglibInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, 
                            Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("代理类在目标方法执行前的操作"+method.getName());
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("代理类在目标方法执行后结果"+result);
        return result;
    }
}
//CGLIB 的Enhancer类来创建代理对象
import net.sf.cglib.proxy.Enhancer;
public class CglibInterceptorMain {
    public static void main(String[] args) {
        //首先创建一个Enhancer对象
        Enhancer enhancer = new Enhancer();
        //设置代理类要继承的目标类、设置方法拦截器
        enhancer.setSuperclass(Calculator.class);
        enhancer.setCallback(new CglibInterceptor());
        //最后通过enhancer.create()创建代理对象将其转换为目标类类型
        Calculator proxy = (Calculator) enhancer.create();
        //代理类对象调用函数;
        proxy.add(1,1);
        proxy.sub(1,1);
    }
}

在这里插入图片描述

🆗,代理模式大致如此,是不是so easy轻轻松松的,求点赞👍、收藏⭐ 🥰🥰🥰

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ue5玩家角色添加武器。切换武器位置,手上武器放到背上。演示一下人体插槽和武器的连接。仅仅演示,实际项目不是这么用的

把第一人称资源包导进来 这就是我们枪的骨骼网格体 我们找到这个骨骼 右手添加插槽 取个名字 因为武器上也有动画&#xff0c;所有武器单独写个蓝图类 新建一个蓝图类 BP_Weapon 把枪的蓝图拖到人的静态网格体下&#xff0c;成为一个部分 选中BP_Weapon的父类套接字…

如何选择适合的证件照制作软件,让您的照片制作更轻松

在当今数字化的时代&#xff0c;制作证件照不再需要专门前往照相馆。选择一款合适的证件照制作软件&#xff0c;您可以在家中轻松完成标准证件照的拍摄与制作。然而&#xff0c;面对市面上琳琅满目的软件&#xff0c;找到最适合您需求的软件并不简单。本文将为您详细介绍选择证…

数据挖掘实训:天气数据分析与机器学习模型构建

随着气候变化对各行各业的影响日益加剧&#xff0c;精准的天气预测已经变得尤为重要。降雨预测在日常生活中尤其关键&#xff0c;例如农业、交通和灾害预警等领域。本文将通过机器学习方法&#xff0c;利用历史天气数据预测明天是否会下雨&#xff0c;具体内容包括数据预处理、…

车载音频开发(二):对音频数据作音量调节

通过前一个章节打下的基础车载音频开发&#xff08;一&#xff09;&#xff1a;从看懂wav开始https://blog.csdn.net/Hellomino_/article/details/140873133?fromshareblogdetail&sharetypeblogdetail&sharerId140873133&sharereferPC&sharesourceHellomino_&…

Apache XMLBeans 一个强大的 XML 数据处理框架

Apache XMLBeans 是一个用于处理 XML 数据的 Java 框架&#xff0c;它提供了一种方式将 XML Schema (XSD) 映射到 Java 类&#xff0c;从而使得开发者可以通过强类型化的 Java 对象来访问和操作 XML 文档。下面将以一个简单的案例说明如何使用 Apache XMLBeans 来解析、生成和验…

74 mysql having 的实现

前言 这里 我们主要是 看一下 having 的相关实现 having 经常是配合 group by 这边进行使用, 进行一个基于 group by 之后的结果的一个, 条件限定 我们这里 以最简单的 group by having 来进行调试, 他会分为 两个阶段, 一个阶段是 group by 之后的结果输出到临时表, 另外…

PyCharm+RobotFramework框架实现UDS自动化测试——(一)python-can 库的安装与环境配置

从0开始学习CANoe使用 从0开始学习车载测试 相信时间的力量 星光不负赶路者&#xff0c;时光不负有心人。 文章目录 1. 概述2.安装 python-can 库—基于pycharm在对应的工程下3. 在任意盘中安装环境4. 导入 can 模块语法5. 配置 CAN 接口6.CANoe设备连接语法 1. 概述 本专栏主…

Java Spring Boot实现基于URL + IP访问频率限制

点击下载《Java Spring Boot实现基于URL IP访问频率限制(源代码)》 1. 引言 在现代 Web 应用中&#xff0c;接口被恶意刷新或暴力请求是一种常见的攻击手段。为了保护系统资源&#xff0c;防止服务器过载或服务不可用&#xff0c;需要对接口的访问频率进行限制。本文将介绍如…

从CentOS到龙蜥:企业级Linux迁移实践记录(系统安装)

引言&#xff1a; 随着CentOS项目宣布停止维护CentOS 8并转向CentOS Stream&#xff0c;许多企业和组织面临着寻找可靠替代方案的挑战。在这个背景下&#xff0c;龙蜥操作系统&#xff08;OpenAnolis&#xff09;作为一个稳定、高性能且完全兼容的企业级Linux发行版&#xff0…

现代企业架构白皮书(可以在线阅读完整PDF文件)

数据架构元模型综述 数据架构的内容元模型包括“结构”、“端口”两个部分&#xff0c;如下图所示&#xff1a; 结构部分用来对数据模型、数据处理建模&#xff0c;其中包括数据对象、数据组件 端口部分用来对数据模型的边界建模&#xff0c;其中包括数据服务 数据架构元模型…

【面试题】技术场景 7、定位系统瓶颈

系统瓶颈定位方法总述 面试官询问如何快速定位系统瓶颈&#xff0c;旨在考察线上调试经验。主要方法包括&#xff1a; 压测&#xff1a;在项目上线前找出系统瓶颈并修复。监控工具或链路追踪工具&#xff1a;项目上线后用于实时监控或评测找瓶颈。Arthas&#xff08;原阿尔萨…

某漫画网站JS逆向反混淆流程分析

文章目录 1. 写在前面1. 接口分析2. 反混淆分析 【&#x1f3e0;作者主页】&#xff1a;吴秋霖 【&#x1f4bc;作者介绍】&#xff1a;擅长爬虫与JS加密逆向分析&#xff01;Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Pyth…

124.【C语言】数据结构之快速排序的小区间优化和非递归的解决方法

目录 1.小区间优化 测试代码 运行结果 2.非递归的解决方法(重要!) 递归产生的问题 一般来说,递归改非递归有两种方法 算法分析 递归产生的二叉树 栈的示意图 先写代码框架 再填写细节部分 1.小区间优化 回顾121.【C语言】数据结构之快速排序(未优化的Hoare排序存在…

赛车微型配件订销管理系统(源码+lw+部署文档+讲解),源码可白嫖!

摘要 赛车微型配件行业通常具有产品多样性、需求不确定性、市场竞争激烈等特点。配件供应商需要根据市场需求及时调整产品结构和库存&#xff0c;同时要把握好供应链管理和销售渠道。传统的赛车微型配件订销管理往往依赖于人工经验和简单的数据分析&#xff0c;效率低下且容易…

Java一个简单的反弹动画练习

文章目录 说明代码详解创建窗体代码创建绘图板创建线程 运行结果完整代码 说明 做了一个小球和星型做反弹动画的窗体作为练习&#xff0c;分享给大家&#xff0c;为了方便和我一样的小白可以看的比较明白&#xff0c;所以尽量详细的标注了注释&#xff0c;希望能帮到同样在学习…

基于YOLOv8的车辆跟踪、车速计算和车辆统计应用

1、环境搭建 通过conda创建一个python≥3.8环境&#xff0c;激活环境后安装ultralytics8.2、python-opencv、shapely>2.0.0: conda create -n yolov8 python3.10 conda activate yolov8 pip install ultralytics8.2 pip install python-opencv pip install shapely>2.0…

如何提升scrapy的效率

如何提升scrapy的效率 在settings配置文件中修改CONCURRENT_REQUESTS 100 scrapy默认开启的线程数量为32个&#xff0c;这样设置可以使其线程数量为100个在运行scrapy时,会有大量的日志信息输出&#xff0c;为了减少cpu的使用率&#xff0c;可以设置log输出信息为WORNING或者…

网络安全 | 网络安全法规:GDPR、CCPA与中国网络安全法

网络安全 | 网络安全法规&#xff1a;GDPR、CCPA与中国网络安全法 一、前言二、欧盟《通用数据保护条例》&#xff08;GDPR&#xff09;2.1 背景2.2 主要内容2.3 特点2.4 实施效果与影响 三、美国《加利福尼亚州消费者隐私法案》&#xff08;CCPA&#xff09;3.1 背景3.2 主要内…

HarmonyOS(ArkUI框架介绍)

ArkUI框架介绍 ArkUI简介 基本概念 UI&#xff1a; 即用户界面。开发者可以将应用的用户界面设计为多个功能页面&#xff0c;每个页面进行单独的文件管理&#xff0c;并通过页面路由API完成页面间的调度管理如跳转、回退等操作&#xff0c;以实现应用内的功能解耦。 组件&…

EasyExcel(二)导出Excel表自动换行和样式设置

EasyExcel(一)导出Excel表列宽自适应 背景 在上一篇文章中解决导出列宽自适应,然后也解决了导出列宽不可超过255的问题。但是实际应用场景中仍然会有导出数据的长度超过列宽255。这时导出效果就会出现如下现象: 多出列宽宽度的内容会浮出来,影响后边列数据的显示。 解决…