java高级:动态代理

2023-11-12

动态代理介绍、准备功能

这节课我们学习一个Java的高级技术叫做动态代理。首先我们认识一下代理长什么样?

假设现在有一个明星坤坤,它有唱歌和跳舞的本领,作为明星是要用唱歌和跳舞来赚钱的,但是每次做节目,唱歌的时候要准备话筒、收钱,再唱歌;跳舞的时候也要准备场地、收钱、再唱歌。明星觉得我擅长的做的事情是唱歌,和跳舞,但是每次唱歌和跳舞之前或者之后都要做一些繁琐的事情,有点烦。于是就找个一个经济公司,请了一个代理人,代理明星处理这些事情,如果有人想请明星演出,直接找代理人就可以了。如下图所示

请添加图片描述
我们说明星的代理是中介公司派的,那中介公司怎么知道,要派一个有唱歌和跳舞功能的代理呢?

解决这个问题,Java使用的是接口,明星想找代理,在Java中需要明星实现了一个接口,接口中规定要唱歌和跳舞的方法。Java就可以通过这个接口为明星生成一个代理对象,只要接口中有的方法代理对象也会有。

请添加图片描述

接下来我们就先把有唱歌和跳舞功能的接口,和实现接口的大明星类定义出来,声明接口是为了声明大明星类中有的方法代理对象也会有,注意,大明星类也需要实现Star接口,这是java生成代理的一个约定。

请添加图片描述

生成动态代理对象

有了上面的准备工作,下面我们需要写一个为BigStar生成动态代理对象的工具类ProxyUtil代表中介机构。使用工具类产生代理则需要用Java为开发者提供的一个生成代理对象的类叫Proxy类。注意Proxy类有多个,我们需要选择java.lang.reflect中的Proxy

通过Proxy类的newInstance(…)方法可以为实现了同一接口的类生成代理对象。 调用方法时需要传递三个参数,该方法的参数解释可以查阅API文档,如下。

请添加图片描述

请添加图片描述

这里代码逻辑比较抽象,所以写了大量的注释来解释逻辑,需要仔细阅读。

public class ProxyUtil {
    //因为是工具类所以可以定义一个静态方法来产生代理,产生谁的代理可以通过方法参数传递
    //这个方法生成的代理肯定是实现了Star接口的对象 所以这里可以将Star作为返回值
    public static Star createProxy(BigStar bigStar){//我们要为bigStar创造代理并返回
       /* newProxyInstance(ClassLoader loader,
                Class<?>[] interfaces,
                InvocationHandler h)
                参数1:用于指定一个类加载器 用于加载生成的代理类 写法是固定的 背就行 一般用当前类的类加载器
                参数2:一个接口数组 指定生成的代理长什么样子 也就是有哪些方法 我们这里只有一个接口 把它包装成数组传进去即可
                参数3:用来指定生成的代理对象要干什么事情 这里传递的是一个InvocationHandler接口
                因为接口不能直接创建对象 所以一般是传递一个匿名内部类对象来指定代理对象干什么事情重写invoke方法就行
                */

        /* invoke方法是个回调方法 会被谁回调呢? 假设代理写好了 调用时是会写这样的代码的:
         * Star starProxy = ProxyUtil.createProxy(s);//得到一个s的代理对象
         * starProxy.sing("好日子") starProxy.dance() 而sing和dance会调用invoke方法!
         * 因为代理干什么事情用invoke决定 invoke需要三个参数 所以sing和dance也会传进这三个参数
         * 比如starProxy.sing("好日子") starProxy是第一个参数 sing是第二个 "好日子"是第三个
         * 第一个参数java把代理对象当做一个Object 也就是starProxy 第二个参数是调用的方法
         * 如果是sing调用 method代表的就是sing方法 第三个参数args会把方法的参数通过一个object数组传进来
         * 比如sing调用时就会把"好日子"传进数组 这就是invoke三个参数的含义
         */
        //newProxyInstance返回的是Object 所以需要强转
        Star starProxy = (Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
                new Class[]{Star.class}, new InvocationHandler() {
                    @Override // 重写invoke回调方法
                    //invoke是重点 代理干什么事情其实是由它决定
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 代理对象要做的事情,会在这里写代码
                        if(method.getName().equals("sing")){
                            System.out.println("准备话筒,收钱20万");
                        }else if(method.getName().equals("dance")){
                            System.out.println("准备场地,收钱1000万");
                        }
                        //代理做完事情 再让明星做他该做的事
                        return method.invoke(bigStar, args);//bigStar代表明星 再把调用方法参数传进来
                        //注意这里的invoke不是这里写的invoke 而是反射里Method提供的invoke方法!
                        //调用sing 则会返回"谢谢大家!
                    }
                });
        return starProxy;//返回代理对象
    }
}

在写一个Test类调用我们写好的ProxyUtil工具类,为BigStar对象生成代理对象:

public class Test {
    public static void main(String[] args) {
        BigStar s = new BigStar("大明星坤坤");
        Star starProxy = ProxyUtil.createProxy(s);

        String rs = starProxy.sing("好日子");
        System.out.println(rs);

        starProxy.dance();
    }
}

运行结果:

请添加图片描述




动态代理应用

学习完动态代理的基本使用之后,接下来我们再做一个应用案例。

请添加图片描述

现有如下代码

/**
 *  用户业务接口
 */
public interface UserService {
    // 登录功能
    void login(String loginName,String passWord) throws Exception;
    // 删除用户
    void deleteUsers() throws Exception;
    // 查询用户,返回数组的形式。
    String[] selectUsers() throws Exception;
}

下面有一个UserService接口的实现类,下面每一个方法中都有计算方法运行时间的代码。

/**
 * 用户业务实现类(面向接口编程)
 */
public class UserServiceImpl implements UserService {
    @Override
    public void login(String loginName, String passWord) throws Exception {
        long time1 = System.currentTimeMillis();
        if ("admin".equals(loginName) && "123456".equals(passWord)) {
            System.out.println("您登录成功,欢迎光临本系统~");
        } else {
            System.out.println("您登录失败,用户名或密码错误~");
        }
        Thread.sleep(1000);
        long time2 = System.currentTimeMillis();
        System.out.println("login方法耗时:" + (time2 - time1) / 1000.0 + "s");
    }

    @Override
    public void deleteUsers() throws Exception {
        long time1 = System.currentTimeMillis();
        System.out.println("成功删除了1万个用户~");
        Thread.sleep(1500);
        long time2 = System.currentTimeMillis();
        System.out.println("deleteUsers方法耗时:" + (time2 - time1) / 1000.0 + "s");
    }

    @Override
    public String[] selectUsers() throws Exception {
        long time1 = System.currentTimeMillis();
        System.out.println("查询出了3个用户");
        String[] names = {"张全蛋", "李二狗", "牛爱花"};
        Thread.sleep(500);
        long time2 = System.currentTimeMillis();
        System.out.println("selectUsers方法耗时:" + (time2 - time1) / 1000.0 + "s");
        return names;
    }
}

我们会发现每一个方法中计算耗时的代码都是重复的,况且这些重复的代码并不属于UserSerivce的主要业务代码。所以接下来我们打算,把计算每一个方法的耗时操作,交给代理对象来做。

先在UserService类中把计算耗时的代码删除,代码如下

public class UserServiceImpl implements UserService {
    @Override
    public void login(String loginName, String passWord) throws Exception {
        if ("admin".equals(loginName) && "123456".equals(passWord)) {
            System.out.println("您登录成功,欢迎光临本系统~");
        } else {
            System.out.println("您登录失败,用户名或密码错误~");
        }
        Thread.sleep(1000);
    }

    @Override
    public void deleteUsers() throws Exception {
        System.out.println("成功删除了1万个用户~");
        Thread.sleep(1500);
    }

    @Override
    public String[] selectUsers() throws Exception {
        System.out.println("查询出了3个用户");
        String[] names = {"张全蛋", "李二狗", "牛爱花"};
        Thread.sleep(500);
        return names;
    }
}

然后为UserService生成一个动态代理对象,在动态代理中调用目标方法,在调用目标方法之前和之后记录毫秒值,并计算方法运行的时间。代码如下

public class ProxyUtil {
    public static UserService creatProxy(UserService userService){
        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
                new Class[]{UserService.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        String methodName=method.getName();
                        if(methodName.equals("login")||methodName.equals("deleteUsers")||methodName.equals("selectUsers")){
                            long startTime = System.currentTimeMillis();
                            Object rs = method.invoke(userService,args);

                            long endTime = System.currentTimeMillis();
                            System.out.println(methodName + "方法执行耗时:"+(endTime-startTime)/1000.0+"s");
                            return rs;
                        }else{
                            Object rs = method.invoke(userService,args);
                            return rs;
                        }
                    }
                });
        return userServiceProxy;
    }
}

然后在测试类中为UserService创建代理对象:

public class Test {
    public static void main(String[] args) throws Exception{
        // 1、创建用户业务对象。
        UserService userService = ProxyUtil.createProxy(new UserServiceImpl());

        // 2、调用用户业务的功能。
        userService.login("admin", "123456");
        System.out.println("----------------------------------");

        userService.deleteUsers();
        System.out.println("----------------------------------");

        String[] names = userService.selectUsers();
        System.out.println("查询到的用户是:" + Arrays.toString(names));
        System.out.println("----------------------------------");

    }
}

执行结果如下所示:

请添加图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

java高级:动态代理 的相关文章

随机推荐

  • python用户名和密码登录_python编写登录接口

    python编写登录接口 一 需求 编写登录接口 1 输入用户名和密码登录 2 输错三次锁定账户 3 下次登录还是上次的账户 提示锁定 直接退出 用到文件读写 4 成功 后显示登录成功 二 需求流程图 三 代码示例 例1 bin bash
  • 解决Windows jmeter Non HTTP response message: Address already in use: connect 错误

    jMeter报错 Response code Non HTTP response code java net BindException Response message Non HTTP response message Address
  • Xgboost算法——Kaggle案例

    作者简介Introduction 苏高生 西南财经大学统计学硕士毕业 现就职于中国电信 主要负责企业存量客户大数据分析 数据建模 研究方向 机器学习 最喜欢的编程语言 R语言 没有之一 E mail sugs01 outlook com 零
  • 图像上的算术运算(opencv-python学习(2))

    图像上的算术运算 图像加法 图像融合 按位运算 图像加法 通过OpenCV函数 cv add 通过numpy操作 res img1 img2 OpenCV加法是饱和运算 而Numpy加法是模运算 x np uint8 250 y np ui
  • [Qt] tcp服务器连接多个客户端的实现

    Qt tcp服务器连接多个客户端的实现 要求 数据按字节接收 以1 255个字节循环发送 编译环境 Qt 5 9 5 客户端的实现 代码如下 TcpClient h ifndef TCPCLIENT H define TCPCLIENT H
  • 【SpringCloud】IDEA如何创建一个SpringCloud项目

    提示 本文包括父工程创建和环境配置 3个子Module为一个简单的订单微服务工程 文章目录 新建一个Maven项目 项目处理 导入依赖 关于SpringBoot SpringCloud SpringCloudAlibaba版本选择 Rest
  • Mongo中append方法使用

    在MongoDB的官网已经很详细的介绍了各种客户端的使用 其中也包括java的 在此 仅对几个比较疑惑的地方做个标注 1 如何向db中添加collection 如果在api文档中找不到答案 那就去看源代码吧 可以看到com mongodb
  • 【解决方案】Ubuntu 20.04.5 LTS报错:“E: Unable to locate package xx”

    问题描述 今天在学习时 出现如下报错 使用的是Ubuntu 20 04 5 LTS lucky DESKTOP VQ8KID4 sudo apt install rs Reading package lists Done Building
  • PAL和NTSC的区别

    1 NTSC制又称为恩制 它属于同时制 是美国在1953年12月首先研制成功的 并以美国国家电视系统委员会 National Television System Committee 的缩写命名 这种制式的色度信号调制特点为平衡正交调幅制 即
  • 当后端一次性丢给你数十万条数据, 作为前端工程师的你,要怎么处理?

    前段时间有朋友问我一个他们公司遇到的问题 说是后端由于某种原因没有实现分页功能 所以一次性返回了2万条数据 让前端用 select 组件展示到用户界面里 我听完之后立马明白了他的困惑 如果通过硬编码的方式去直接渲染这两万条数据到 selec
  • 棋盘格文件及标定矫正程序(链接直接下载,CAD文件可修改)

    一个27 27mm棋盘格文件 9x6格 标定摄像头想下载棋盘格图片 找的太难了 用CAD画了一个 打印出来A4纸上实际尺寸27 27mm 需要的可以点击下载 链接 棋盘格下载地址 1 里面有CAD文件方便修改其它需要的尺寸 格数 也可以直接
  • HLL 算法(HyperLogLog)

    HyperLogLog 下面简称为HLL 它是 LogLog 算法的升级版 作用是能够提供不精确的去重计数 存在以下的特点 代码实现较难 能够使用极少的内存来统计巨量的数据 在 Redis 中实现的 HyperLogLog 只需要12K内存
  • c++基础十(流程结构)

    目录 程序流程结构 1 选择结构 1 1 作用 1 2 if 1 2 1 单行格式 1 2 2 多行格式 1 2 3 多条件格式 1 3 三目运算符 1 4 switch 1 5 小结 2 循环结构 2 1 作用 2 2 while 2 3
  • 模拟微信小程序加载动画

  • Loader Runner11 安装、汉化与破解

    1 安装 可能会安装所需组件请自行安装 更改安装目录 开始安装 安装完成后会提示只能试用10天 2 汉化 如果需要汉化先汉化再破解 否则汉化之后还需要重新破解 以解压包的方式打开汉化包 iso 汉化过程同英文版LoadRunner安装过程
  • Kafka生产者——向 Kafka写入数据

    不管是把 Kafka 作为消息队列 消息 总线还是数据存储平台来使用 总是需要有一个可以往 Kafka 写入数据的生产者和一个可以从 Kafka读取数据的消费者 或者一个兼具两种角 色的应用程序 例如 在一个信用卡事务处理系统里 有一个客户
  • 浏览器是如何运作【前端必备】

    浏览器是如何运作的 1 进程与线程 2 浏览器结构 2 1用户界面 2 2多进程浏览器结构 浏览器是如何运作的 浏览器是运行在操作系统上的一个应用程序 每个应用程序必须至少启动一个进程来执行其功能 每个程序往往需要运行很多任务 进程就会创建
  • layui jquery项目中预览出word文件

    最近的需求 要求在后台管理系统中列表预览出用户上传的文件 如下图展示
  • Python调用百度API语音识别实现一个简单的语音识别程序

    之前在网上看到一个题目使用语音控制你的浏览器 感觉挺有意思的 就想着实现一个简单的语音识别程序 这里我选择的是百度语音识别 还有好多不错的如科大讯飞等都可以使用 语音识别过程分为三个部分 1 录音 2 获取参数access token 有效
  • java高级:动态代理

    目录 动态代理介绍 准备功能 生成动态代理对象 动态代理应用 动态代理介绍 准备功能 这节课我们学习一个Java的高级技术叫做动态代理 首先我们认识一下代理长什么样 假设现在有一个明星坤坤 它有唱歌和跳舞的本领 作为明星是要用唱歌和跳舞来赚