Java的动态代理(实际案例秒懂!)

news2024/9/23 5:30:46

在看动态代理解决两个案例之前,请先看链接VCR

《java代理》2分钟动画_哔哩哔哩_bilibili

一.动态代理-精致小案例

需求分析

传统方法

就是定义一个接口,然后实现类去实现规定的run方法

缺点:代码很冗余,有一些运行前和运行后的代码都给了实现类,难以管理

 

 动态代理方法

使用动态代理,利用反射机制,根据方法决定调用哪个对象的方法

添加VehicleProxyProvider类

package me;


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

/**
 * @version 1.0
 * VehicleProxyProvider 该类可以返回一个代理对象.
 */
public class VehicleProxyProvider {

    //定义一个属性
    //target_vehicle 表示真正要执行的对象
    //该对象实现了Vehicle接口
    private Vehicle target_vehicle;

    //构造器
    public VehicleProxyProvider(Vehicle target_vehicle) {
        this.target_vehicle = target_vehicle;
    }

    //编写一个方法,可以返回一个代理对象, 该代理对象可以通过反射机制调用到被代理对象的方法
    public Vehicle getProxy() {

        //得到类加载器
        ClassLoader classLoader =
                target_vehicle.getClass().getClassLoader();

        //得到要代理的对象/被执行对象 的接口信息,底层是通过接口来完成调用
        Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();


        //创建InvocationHandler 对象
        //因为 InvocationHandler 是接口,所以我们可以通过匿名对象的方式来创建该对象
        /**
         *
         * public interface InvocationHandler {
         *  public Object invoke(Object proxy, Method method, Object[] args)
         *         throws Throwable;
         * }
         * invoke 方法是将来执行我们的target_vehicle的方法时,会调用到
         *
         */

        InvocationHandler invocationHandler = new InvocationHandler() {
            /**
             * invoke 方法是将来执行我们的target_vehicle的方法时,会调用到
             * @param o 表示代理对象
             * @param method 就是通过代理对象调用方法时,的哪个方法 代理对象.run()
             * @param args : 表示调用 代理对象.run(xx) 传入的参数
             * @return 表示 代理对象.run(xx) 执行后的结果.
             * @throws Throwable
             */
            @Override
            public Object invoke(Object o, Method method, Object[] args)
                    throws Throwable {

                System.out.println("交通工具开始运行了....");
                //这里是我们的反射基础 => OOP
                //method 是?: public abstract void com.hspedu.spring.proxy2.Vehicle.run()
                //target_vehicle 是? Ship对象
                //args 是null
                //这里通过反射+动态绑定机制,就会执行到被代理对象的方法
                //执行完毕就返回
                Object result = method.invoke(target_vehicle, args);
                System.out.println("交通工具停止运行了....");
                return result;
            }
        };

        /*

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

          老师解读
          1. Proxy.newProxyInstance() 可以返回一个代理对象
          2. ClassLoader loader: 类的加载器.
          3. Class<?>[] interfaces 就是将来要代理的对象的接口信息
          4. InvocationHandler h 调用处理器/对象 有一个非常重要的方法invoke
         */
        Vehicle proxy =
                (Vehicle) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

        return proxy;
    }
}

最重要的代码就是 :

Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);

这是提供最后代理对象实例的方法:参数有三:

        @你要求代理对象的类加载器

        @你要求代理对象的接口信息

        @以及调用处理器

底层是用反射机制来实现的。

测试方法:

public class VehicleProxyTest {
    @Test
    public void test01()
    {
        Vehicle vehicle = new Ship();
        //传入我们需要代理的对象
        VehicleProxyProvider vehicleProxyProvider = new VehicleProxyProvider(vehicle);
        //获取代理对象
        Vehicle proxy = vehicleProxyProvider.getProxy();
        //通过代理对象执行对应的run()方法
        proxy.run();
    }
}

以及真正的运行结果:
 

 真正解析这个代码

所谓的这个代理,就可以将我们的Vehicle接口看做是一个老板。我们的car类和ship类就是员工。现在老板给这两个员工一个run的工作,但是因为在run工作的前后他们都要输出相同的一句话。这个输出同一句话没必要让每个员工都写一遍,于是就把这个输出同一句话的事情交给了一个代理对象,帮我们输出这句话。代理对象就是老板的助理。

 


这个代码提供代理对象的类:

当你调用proxy.run()方法的时候:会进入到调用器InvocationHandler的invoke方法去

这个调用器中的Object o就是你传入的ship对象,method就是run()方法,arg就是run()里面的参数。

Object result = method.invoke(target_vehicle, args);

方法.invoke(对象)这是反射调用对象ship的run()方法。返回一个结果,

 可以自己去debug一下这个代码。

二.动态代理的深入[横切关注点]

需求分析

 

传统的方法

定义接口:

package proxy;

/**
 * @version 1.0
 * 接口
 */
public interface SmartAnimalable {
    //求和
    float getSum(float i, float j);
    //求差
    float getSub(float i, float j);
}

 传统的实现子类

package proxy;

/**
 * @version 1.0
 */
public class SmartDog implements SmartAnimalable {
    @Override
    public float getSum(float i, float j) {
        //System.out.println("日志-方法名-getSum-参数 " + i + " " + j);
        float result = i + j;
        System.out.println("方法内部打印result = " + result);
        //System.out.println("日志-方法名-getSum-结果result= " + result);
        return result;
    }

    @Override
    public float getSub(float i, float j) {
        //System.out.println("日志-方法名-getSub-参数 " + i + " " + j);
        float result = i - j;
        System.out.println("方法内部打印result = " + result);
        //System.out.println("日志-方法名-getSub-结果result= " + result);
        return result;
    }
}

结果:

 

动态代理的方法 

MyProxyProvider

package proxy;

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

/**
 * @version 1.0
 * 可以返回一个动态代理对象, 可以执行SmartDog对象的方法
 */
public class MyProxyProvider {

    //定义我们要执行的目标对象, 该对象需要实现SmartAnimalable
    private SmartAnimalable target_obj;

    //构造器
    public MyProxyProvider(SmartAnimalable target_obj) {
        this.target_obj = target_obj;
    }

    //方法, 可以返回代理对象,该代理对象可以执行目标对象
    public SmartAnimalable getProxy() {

        //1. 先到的类加载器/对象
        ClassLoader classLoader = target_obj.getClass().getClassLoader();

        //2. 得到要执行的目标对象的接口信息
        Class<?>[] interfaces = target_obj.getClass().getInterfaces();

        //3. 创建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.asList(args)); //这里从AOP看,就是一个横切关注点-前置通知
                    //使用反射调用方法
                    result = method.invoke(target_obj, args);
                    System.out.println("方法执行正常结束-日志-方法名-" + method.getName() + "-结果result= "
                            + result);//从AOP看, 也是一个横切关注点-返回通知

                } catch (Exception e) {
                    e.printStackTrace();
                    //如果反射执行方法时,出现异常,就会进入到catch{}
                    System.out.println("方法执行异常-日志-方法名-" + method.getName()
                            + "-异常类型=" + e.getClass().getName());//从AOP看, 也是一个横切关注点-异常通知
                } finally {//不管你是否出现异常,最终都会执行到finally{}
                    //从AOP的角度看, 也是一个横切关注点-最终通知
                    System.out.println("方法最终结束-日志-方法名-" + method.getName());
                }

                return result;
            }
        };

        //创建代理对象
        SmartAnimalable proxy =
                (SmartAnimalable)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
        return proxy;
    }
}

 Test方法

  @Test
    public void smartDogTestByProxy() {

        SmartAnimalable smartAnimalable = new SmartDog();

        MyProxyProvider myProxyProvider =
                new MyProxyProvider(smartAnimalable);

        //我们返回了代理对象
        SmartAnimalable proxy =
                myProxyProvider.getProxy();

        proxy.getSum(10, 2);
        System.out.println("====================");
        proxy.getSub(10, 2);

    }

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

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

相关文章

C++ | Leetcode C++题解之第385题迷你语法分析器

题目&#xff1a; 题解&#xff1a; class Solution { public:int index 0;NestedInteger deserialize(string s) {if (s[index] [) {index;NestedInteger ni;while (s[index] ! ]) {ni.add(deserialize(s));if (s[index] ,) {index;}}index;return ni;} else {bool negati…

超实用!如何用搜索引擎提升你的工作效率

平常用的Google浏览器比较多&#xff0c;所以分享下Google Chrome一些能提高工作效率的配置和操作以及如何巧妙利用Google搜索技巧快速找我们想要的数据 1. 浏览器设置 1. 搜索引擎快速切换 使用效果&#xff1a; 2. 历史记录快速打开 效果展示&#xff1a; 3. 隐藏显示书…

线性约束最小方差准则(LCMV)波束形成算法及MATLAB深入仿真分析

阵列信号处理——线性约束最小方差准则(LCMV)波束形成算法及MATLAB深入仿真分析 目录 前言 一、LCMV算法 二、仿真参数设置 三、抗干扰权值计算仿真 四、不同干扰方位下抗干扰性能仿真 五、不同信噪比和干噪比下抗干扰性能仿真 总结 前言 在信号处理模块中&#xff0c;通…

Vue——认识day06_class与style绑定

在Vue中&#xff0c;可以使用v-bind指令来将CSS样式动态地绑定到HTML元素上。有两种方式可以实现CSS与style的绑定&#xff1a; 对象语法&#xff1a;可以将一个包含CSS属性和值的对象传递给v-bind&#xff0c;将对象的属性与HTML元素的style属性进行绑定。例如&#xff1a; …

使用 Docker 搭建企业级私有仓库HARBOR

目录 1 HARBOR 的获取 1.1 下载软件包地址 1.2 HARBOR 的介绍 2 部署harbor 2.1 仓库端操作 2.1.1 修改harbor配置文件 2.1.2 生成服务端的证书与秘钥 2.1.3 管理HARBOR 2.1.4 查看是否运行 2.2 客户端操作 2.2.1 证书拷贝给客户端 2.2.2 环境配置 2.2.3 批量读取本地镜像 2.2…

Transformer面试真题详解——覆盖99%的Transformer面试问题(建议收藏)

文章目录 1. 请简述一下Transformer的基本结构和原理2. Transformer为什么使用多头注意力机制3. Transformer计算attention为什么选择点乘而不是加法&#xff1f;两个计算复杂度和效果上有什么区别&#xff1f;4. 为什么在softmax之后要对attention进行scaled&#xff08;为什么…

dubbo之时间轮算法分析

文章目录 前言一、参数说明二、具体实现1、HashedWheelTimer2、createWheel3、newTimeout4、start5、run6、waitForNextTick7、transferTimeoutsToBuckets8、expireTimeouts 总结 前言 时间轮&#xff08;TimingWheel&#xff09;是一种高效利用线程资源进行批量化调度的算法&…

ffmpeg音频编码

音视频播放的流程 根据我之前的文章 我们已经从解复用&#xff0c;解码得到原始数据&#xff0c;现在我们逆向&#xff0c;将frame转化packet。也就是原始数据转化为压缩后的数据文件。 介绍 PCM样本格式 PCM(Pulse Code Modulation&#xff0c;脉冲编码调制)⾳频数据是未经…

离散数学------关系理论

一、序偶和笛卡尔积 序偶 两个序偶如果相等&#xff0c;那么他们相对应的第一第二元素分别相等 笛卡尔积 笛卡尔积是集合之间的一种运算&#xff0c;运算的结果是个序偶&#xff0c;第一元素来自前面的集合&#xff0c;第二元素来自后面的集合。 两集合进行笛卡尔积运算后集合…

UE5学习笔记20-给游戏添加声音

一、准备音频资源 1.Jump文件夹中有跳跃的音频资源wav文件夹中是SoundCue的音波资源 2.音乐衰减文件&#xff0c;右键->音频->音效衰减 二、 在对应的动画资源处将音频添加 1.找到对应的动画帧 2.在对应的行右键添加通知->播放音效 3、选中添加的音效选择对应的音频资…

拦截通信助理,拦截小秘书技术

有人叫做空号识别&#xff0c;有人称为彩铃识别&#xff0c;磐石云通过嵌入软交换进行实时识别前期媒体 案例&#xff1a; 王总公司有20坐席的员工回访用户服务满意度业务&#xff0c;由于用户开通了语音秘书和通信助理&#xff0c;漏话提醒等等&#xff0c;坐席拨打时对方由…

【HTML】开源模拟输入框动画

代码地址&#xff1a; https://uiverse.io/eslam-hany/strange-goose-48代码地址&#xff1a; https://uiverse.io/vnuny/moody-swan-60代码地址&#xff1a; https://uiverse.io/boryanakrasteva/hard-pig-16代码地址&#xff1a; https://uiverse.io/Harsha2lucky/lovely…

TCP和UDP的主要区别以及应用场景

目录 1.主要区别 2.应用场景 1.主要区别 TCP&#xff08;Transmission Control Protocol&#xff09;&#xff1a;有连接&#xff0c;可靠传输&#xff0c;面向字节流&#xff0c;全双工通讯&#xff1b; UDP&#xff08;User Datagram Protocol&#xff09;&#xff1a;无连…

树莓派的启动

我的板子是树莓派3B。 [ 0.000000] Booting Linux on physical CPU 0x0 [ 0.000000] Linux version 6.6.31rpt-rpi-v7 (sergeraspberrypi.com) (gcc-12 (Raspbian 12.2.0-14rpi1) 12.2.0, GNU ld (GNU Binutils for Raspbian) 2.40) #1 SMP Raspbian 1:6.6.31-1rpt1 (202…

【C++ Primer Plus习题】8.6

问题: 解答: #include <iostream> using namespace std;template <typename T> T maxn(T arr[], int len)//通用 {T max 0;for (int i 0; i < len; i){if (max < arr[i]){max arr[i];}}return max; }template<> const char* maxn<const char*&g…

SpringBoot+Vue实现大文件上传(断点续传-后端控制(一))

SpringBootVue实现大文件上传&#xff08;断点续传&#xff09; 1 环境 SpringBoot 3.2.1&#xff0c;Vue 2&#xff0c;ElementUI&#xff0c;spark-md5 2 问题 在前一篇文章&#xff0c;我们写了通过在前端控制的断点续传&#xff0c;但是有两个问题&#xff0c;第一个问题&…

怎么在Windows操作系统部署阿里开源版通义千问(Qwen2)

怎么在Windows操作系统部署阿里开源版通义千问&#xff08;Qwen2&#xff09; | 原创作者/编辑&#xff1a;凯哥Java | 分类&#xff1a;人工智能学习系列教程 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; GitHub上qwen2截图 随着人工智能技术的不断…

【华为】轻松get!eNSP登录无线AC Web界面的新姿势

【华为】轻松get&#xff01;eNSP登录无线AC Web界面的新姿势 无线AC&#xff1a;web界面实验准备华为云配置01 拉取设备02添加UDP端口03再添加VMnet1(VMnet8 也行)网段连接AC的端口04最后设置端口映射 无线AC配置01拉取AC设备和连接华为云02配置AC的g0/0/1端口&#xff08;SVI…

AI时代,需要什么样的服务器操作系统?

文&#xff5c;刘俊宏 编&#xff5c;王一粟 AI时代&#xff0c;中国的服务器系统正在面临一场双重挑战。 今年6月底&#xff0c;最主流的开源服务器操作系统CentOS正式停服&#xff0c;找一个合适的操作系统进行迁移成为了必选项。同时&#xff0c;AI时代的到来&#xff0c…

笔记:《利用Python进行数据分析》之数据聚合

观前提示&#xff1a;这节内容不多&#xff0c;但难度较大&#xff0c;尤其是要能熟练运用时很不容易的 数据聚合 聚合指的是任何能够从数组产生标量值的数据转换过程。之前的例子已经用过一些&#xff0c;比如mean、count、min以及sum等。你可能想知道在GroupBy对象上调用me…