软件设计模式 | 动态代理模式

news2024/10/5 9:32:13

文章目录

  • 一、动态代理概述
    • 1.1 代理的概述和作用
    • 1.2 动态代理的优点
    • 1.3 代理对象的创建
    • 1.4 代理对象调用方法的执行流程
  • 二、动态代理举例
    • 2.1 歌手经纪人
    • 2.2 业务功能的性能统计
    • 2.3 动态代理在 Spring 框架中的应用
  • 三、基于子类的动态代理


一、动态代理概述

1.1 代理的概述和作用

  • 什么是代理?
    代理指某些场景下对象会找一个代理对象,来辅助自己完成一些工作。如:歌星(经济人),买房的人(房产中介)

  • 代理主要干什么工作,是如何工作的?
    是对对象的行为做一些辅助的操作。

  • 代理举例:
    歌手刚出道时,有人花钱让他唱歌,承诺先付首款再付尾款。那么,歌手的整个工作的流程是:收首款、唱歌、收尾款。
    歌手成名以后,业务越来越多,开始雇佣了经纪人。经纪人主要负责:收首款、收尾款以及调用歌手去唱歌,歌手只负责唱歌。
    在这里插入图片描述


1.2 动态代理的优点

  • 非常的灵活,支持任意接口类型的实现类对象做代理,也可以直接为接本身做代理。
  • 可以为被代理对象的所有方法做代理。
  • 不仅简化了编程工作、提高了软件系统的可扩展性,同时也提高了开发效率。
  • 可以在不改变方法源码的情况下,实现对方法功能的增强
    方法增强的理解:歌手原本的工作是唱歌,唱歌前后的收首款和尾款方法就相当于对原本的方法的增强。

1.3 代理对象的创建

  • java 中代理的代表类是: java.lang.reflect.Proxy

  • Proxy 提供了一个静态方法,用于为对象产生一个代理对象返回。

    在这里插入图片描述
    参数二理解:因为客户是通过代理对象去调用歌手的唱歌方法,因此代理类需要接口的列表


1.4 代理对象调用方法的执行流程

  1. 先走向代理
  2. 代理可以为方法额外做一些辅助工作
  3. 开发真正触发对象的方法的执行
  4. 回到代理中,由代理负责返回结果给方法的调用者

二、动态代理举例

2.1 歌手经纪人

项目包结构:

在这里插入图片描述

技能接口:

public interface Skill {
    void jump();
    void sing();
}

明星类:

public class Star implements Skill{
    private String name;
    public Star(String name) {
        this.name = name;
    }

    @Override
    public void jump() {
        System.out.println(name + "开始跳舞");
    }

    @Override
    public void sing() {
        System.out.println(name + "开始唱歌");
    }
}

明星代理类:

public class StarAgentProxy {
    /**
     * 设计一个方法来返回 一个明星对象 的 代理对象
     *      参数一:定义代理类的类加载器
     *      参数二:代理类要实现的接口列表
     *      参数三:将方法调用分派到的处理程序
     */
    public static Skill getProxy(Star obj){
        // 为张三这个对象,生成代理对象
        return (Skill) Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(), new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("收首款");
                        // proxy 代理对象的引用
                        // method 正在调用的方法对象
                        // args 代表这个方法的参数
                        Object rs = method.invoke(obj, args); // 采用反射机制,如果没有返回值,则返回null
                        System.out.println("收尾款");
                        return rs;
                    }
                });
    }
}

客户端模拟:

public class Test {
    /**
     * 理解动态代理
     */
    public static void main(String[] args) {
        // 1. 创建一个对象
        Star star = new Star("张三");
        // 为张三对象,生成一个代理对象(经纪人)
        Skill proxy = StarAgentProxy.getProxy(star);
        proxy.jump();
        System.out.println("--------");
        proxy.sing();
    }
}

输出结果:

在这里插入图片描述


2.2 业务功能的性能统计

需求: 模拟某企业用户管理业务,需包含用户登录、删除、查询功能,并要统计每个功能的耗时

项目包结构:

在这里插入图片描述

用户业务层接口:

public interface UserService {
    String login(String loginName, String password);
    void delete(Integer id);
    void selectUsers();
}

用户业务层实现类:

public class UserServiceImpl implements UserService{
    @Override
    public String login(String loginName, String password) {
        String rs = "登录名称或者密码错误!";
        if("admin".equals(loginName) && "123456".equals(password)){
            rs = "登录成功";
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return rs;
    }

    @Override
    public void delete(Integer id) {
        try {
            System.out.println("正在删除数据中");
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void selectUsers() {
        System.out.println("查询了100个用户数据!");
        try {
            Thread.sleep(3000);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

代理对象工具类:

public class ProxyUtil {
    /**
     * 通过静态方法为用户业务对象返回代理对象
     */
    public static UserService getProxy(UserService obj){
        return (UserService) Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                obj.getClass().getInterfaces(), new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        long startTime = System.currentTimeMillis();

                        // 真正触发对象的行为执行
                        Object rs = method.invoke(obj, args);

                        long endTime = System.currentTimeMillis();
                        System.out.println(method.getName() + "方法耗时:" + (endTime-startTime)/1000.0 + "s");
                        return rs;
                    }
                });
    }
}

客户端模拟:

public class Test {
    public static void main(String[] args) {
        UserService proxy = ProxyUtil.getProxy(new UserServiceImpl());
        proxy.login("admin", "123456");
        proxy.delete(1);
        proxy.selectUsers();
    }
}

输出结果:

在这里插入图片描述


2.3 动态代理在 Spring 框架中的应用

转账方法的事务问题回顾:

参考:Spring 从入门到精通系列 09 —— 转账方法的事务问题与动态代理

转账业务流程中,如果中间的某一部分业务出现异常,那么会导致异常后的事务不会执行,从而引发账户出错的严重情况。

在这里插入图片描述
问题的关键在于:
整个业务方法一共获取了四次数据库连接对象,有四个业务需要处理。当前事务完成后,会直接提交事务。那么当某个事务出现异常时,只对他自己的事务进行回滚,对其他的事务不回滚。

当时提出的解决方案是:
由于整体的业务属于一个线程,那么通过使用 ThreadLocal 对象把 Connection 连接对象和当前线程绑定,即使一个线程中只有一个 Connection 对象,而不是原本的四个。(要么都发生,要么都不发生)

更新事务控制后,具体实现如下:

在这里插入图片描述
代码变的很复杂,而且每个方法都需要加上:开启事务、提交事务…等事务处理。因此,可利用动态代理的技术进行处理。
Spring 的 AOP 的实现思想就是动态代理,即 在不修改源码的基础上对已有方法进行增强。


三、基于子类的动态代理

上文讲述的代理模式属于 基于接口的动态代理模式,当其不实现任何接口时,该动态代理对象不能得到。
但是 基于子类的动态代理 可以实现不用实现接口的情况下实现代理。

导入依赖:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.1_3</version>
</dependency>

明星类:

public class Star{
    private String name;

    public Star() {
    }

    public Star(String name) {
        this.name = name;
    }

    public void jump() {
        System.out.println(name + "开始跳舞");
    }

    public void sing() {
        System.out.println(name + "开始唱歌");
    }
}

明星代理类:

public class ProxyUtil {
    /**
     * 通过静态方法为用户业务对象返回代理对象
     */
    public static Star getProxy(Star obj){
        return (Star) Enhancer.create(obj.getClass(), new MethodInterceptor() {
            /**
             * 执行被代理对象的任何方法都会经过该方法
             * @param proxy
             * @param method
             * @param args
             *    以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
             * @param methodProxy :当前执行方法的代理对象
             */
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("收首款");
                Object rs = method.invoke(obj, args);
                System.out.println("收尾款");
                return rs;
            }
        });
    }
}

客户端模拟:

public class Test {
    public static void main(String[] args) {
        final Star star = new Star("张三");
        Star proxy = ProxyUtil.getProxy(star);
        proxy.jump();
        System.out.println("--------");
        proxy.sing();
    }
}

输出结果:

在这里插入图片描述

注:基于子类的动态代理,被代理类必须实现无参构造(当被代理类中有参构造函数时,需重写无参构造),否则报以下异常。

在这里插入图片描述

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

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

相关文章

英语语法——动词

文章目录分类时态一般时态三单过去式进行时态现在分词完成时态过去分词完成进行时态情态语态否定强调非谓语动词分类 时态 时态用于表示动作发生的时间和状态&#xff0c;它通过谓语动词的形式变化来体现&#xff0c;时态是每个动词必须要有的。在英语中有以下十六种时态&…

MySQL怎么卸载干净

卸载干净MySQL的步骤&#xff1a;首先关闭MySQL服务&#xff1b;然后卸载MySQL软件&#xff1b;接着删除MySQL在电脑中的残存文件&#xff1b;最后删除MySQL注册表信息即可。 很多人都遇到过一个问题&#xff0c;那就是在安装MySQL的时候操作错误&#xff0c;最后结果不是自己想…

C++【深入理解继承】

文章目录一、继承概念与定义二、基类和派生类对象赋值转换三、派生类的默认成员函数&#xff08;1&#xff09;构造函数&#xff08;2&#xff09;拷贝构造函数&#xff08;3&#xff09;赋值重载&#xff08;4&#xff09;析构函数四、复杂的菱形继承及菱形虚拟继承&#xff0…

python 不指定参数个数---args基础用法

前言&#xff1a; 在有些时候&#xff0c;设计函数的时候&#xff0c;可能不知道要传入的参数类型或者参数个数&#xff0c;此时args可以很好地解决。 一、*args的基本用法 1。传入不指定个数的参数&#xff0c; 2。参数的类型也不指定&#xff0c;可以是任意类型数据&…

k8s学习(三十五)飞腾2000+麒麟V10离线部署metrics-server

文章目录1、下载metrics-server配置文件2、下载推送metrics-server镜像3、修改metrics-server配置4、启动metrics-server1、下载metrics-server配置文件 在有网机器上从网站https://github.com/kubernetes-incubator/metrics-server下载 拷贝其到离线机器K8S的master节点。 2…

九龙证券|又3个涨停,退市风险急升!

*ST新海退市危险急剧上升&#xff01; 到4月14日&#xff0c;*ST新海收盘价接连14个买卖日低于1元/股。按照退市新规&#xff0c;若*ST新海在接下来6个买卖日收盘价继续低于1元/股&#xff0c;将触及买卖类强制退市景象而终止上市&#xff0c;公司股票将不进入退市整理期。 面…

Android Audio音量设置原理流程分析

Android Audio音量设置原理流程分析 简介 本篇文章主要介绍Android音量设置从App应用层到framework层执行流程&#xff0c;以及相关的细节和原理分析&#xff0c;建议在阅读此文章前去看博主的混音理论篇的声音的音量属性和声音相关公式的推导章节&#xff0c;这对阅读时理解音…

2023年泰迪杯数据挖掘挑战赛B题完整数据分析与预测(5.针对完整数据的组合预测-机器学习+深度学习)

背景 2023年泰迪杯完整数据最新出炉&#xff0c;博主根据最新完整数据对原来的预测方案进行了调整&#xff0c;采用机器学习深度学习的组合预测来实现最终预测 全部数据已经出炉&#xff0c;可以看出训练样本和预测样本都增加了十倍&#xff0c;这对于数据的处理复杂程度也有…

linux驱动开发 - 04_Linux 设备树学习 - DTS语法

文章目录Linux 设备树学习 - DTS语法1 什么是设备树&#xff1f;2 DTS、DTB和DTC3 DTS 语法3.1 dtsi 头文件3.2 设备节点3.3 标准属性1、compatible 属性2、model 属性3、status 属性4、#address-cells 和#size-cells 属性5、reg 属性6、ranges 属性7、name 属性8、device_type…

FreeRTOS 任务切换

文章目录一、PendSV 异常二、FreeRTOS 任务切换场合1. 执行系统调用 taskYIELD()2. 系统滴答定时器(SysTick)中断 SysTick_Handler三、PendSV 中断服务函数 PendSV_Handler()四、查找下一个要运行的任务 vTaskSwitchContext()五、FreeRTOS 时间片调度六、时间片调度实验RTOS 系…

ECF机制:信号 (Signal)

&#x1f4ad; 写在前面&#xff1a;ECF (异常控制流) 机制是存在于系统的所有层级中的&#xff0c;所以这一块的知识我们需要系统地去学习。前几章我们探讨过了异常 (Exceptions)&#xff0c;由硬件触发&#xff0c;在内核代码中处理。讲解了进程的上下文切换 (Process Contex…

Shiro整合SpringBoot项目实战

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

阿里入局,通义千问备受期待

目录官宣内测体验内容鸟鸟分鸟后言继百度文心一言发布三周之后&#xff0c;4月7日阿里通义大模型终于推出通义千问&#xff0c;阿里正式加入ChatGPT战局。下午市场一片大热&#xff0c;对于深耕NLP多年的阿里&#xff0c;大家有足够的期待。 官宣内测 “你好&#xff0c;我叫通…

【SpringBoot】springboot启动热部署

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ SpringBoot——手工启动热部署一、pom.xml导入…

Kotlin 是后端开发的未来

Kotlin 是后端开发的未来 严格类型、命名参数、多范式语言 您今天遇到的每个后端开发人员都会说他们使用 JavaScript、Python、PHP 或 Ruby 编写代码。近年来&#xff0c;您会遇到一小部分人转而使用 Kotlin 作为他们创建 Web 服务器的语言选择。由于我在学习Ktor&#xff0c;所…

深度学习12. CNN经典网络 VGG16

深度学习12. CNN经典网络 VGG16一、简介1. VGG 来源2. VGG分类3. 不同模型的参数数量4. 3x3卷积核的好处5. 关于学习率调度6. 批归一化二、VGG16层分析1. 层划分2. 参数展开过程图解3. 参数传递示例4. VGG 16各层参数数量三、代码分析1. VGG16模型定义2. 训练3. 测试一、简介 …

Html5版音乐游戏制作及分享(H5音乐游戏)

这里实现了Html5版的音乐游戏的核心玩法。 游戏的制作借鉴了&#xff0c;很多经典的音乐游戏玩法&#xff0c;通过简单的代码将音乐的节奏与操作相结合。 可以通过手机进行游戏&#xff0c;准确点击下落时的目标&#xff0c;进行得分。 点击试玩 游戏内的下落数据是通过手打记…

【Pytorch】使用pytorch进行张量计算、自动求导和神经网络构建

本文参加新星计划人工智能(Pytorch)赛道&#xff1a;https://bbs.csdn.net/topics/613989052 这是目录张量计算张量的属性和方法&#xff0c;如何使用它们来获取或修改张量的信息和形状张量之间的运算和广播机制&#xff0c;如何使用torch.add(), torch.sub(), torch.mul(), to…

【Redis7】Redis7 持久化(重点:RDB与AOF重写机制)

【大家好&#xff0c;我是爱干饭的猿&#xff0c;本文重点介绍Redis7 持久化&#xff08;重点&#xff1a;RDB与AOF重写机制&#xff09;。 后续会继续分享Redis7和其他重要知识点总结&#xff0c;如果喜欢这篇文章&#xff0c;点个赞&#x1f44d;&#xff0c;关注一下吧】 …

Java项目实战笔记(瑞吉外卖)-4

公共字段自动填充功能 问题分析 前面已经完成了后台系统的员工管理功能开发&#xff0c;在新增员工时需要设置创建时间、创建人、修改时间、修改人等字段&#xff0c;在编辑员工时需要设置修改时间和修改人等字段。这些字段属于公共字段&#xff0c;也就是很多表中都有这些字段…