111-Java动态代理[拓展内容:重难点]

本文介绍了Java动态代理的概念,通过实例展示了如何使用Java的`java.lang.reflect.Proxy`创建代理对象,以实现对目标对象的行为增强。动态代理在不修改原有代码的情况下,能够为方法添加额外的功能,如性能统计。案例中,通过动态代理优化了用户管理业务的性能统计,避免了重复代码,提高了开发效率。
摘要由CSDN通过智能技术生成

动态代理[拓展:重难点]

一、概述、快速入门

1、代理是啥?
  • 指的是某些场景下对象会找一个代理对象,来辅助自己完成一些工作。
    • 比如歌星找经纪人、买房的人找房产中介。

2、代理主要干什么?如何工作的?
  • 举例说明

    • 比如对象是杨超越,她还没有火之前,别人叫她去唱歌、跳舞,都是直接叫杨超越对象本人的;

      在这里插入图片描述

    • 那她火了之后,别人想要叫她去唱歌、跳舞,就不能直接去联系杨超越(对象)本人了,而是联系杨超越(对象)的经纪人(代理),那她的经纪人就要收别人的首付款,等表演完,再收尾款。

      在这里插入图片描述




  • 代理的工作流程

    在这里插入图片描述


代理主要是对 对象的行为额外做一些辅助操作



3、如何创建代理对象
  • Java中代理的代表类是:java.lang.reflect.Proxy。

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

    在这里插入图片描述


(1)范例
package com.app.d10_proxy;

/**
 * 1、创建接口:规定明星对象的行为方法
 */
public interface Skill {
    void sing();    // 唱歌
    void jump();    // 跳舞
}
package com.app.d10_proxy;

/**
 * 2、创建明星类:实现Skill接口,实现明星的行为方法
 */
public class Star implements Skill {
    private String name;    // 明星的姓名

    public Star(){

    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    /**
     * a.实现明星的唱歌行为
     */
    @Override
    public void sing() {
        System.out.println(name + "开始唱歌:我和我的祖国,一刻都不能分离~~");
    }

    /**
     * b.实现明星的跳舞行为
     */
    @Override
    public void jump() {
        System.out.println(name + "开始跳舞,跳的很迷人~~");
    }
}
package com.app.d10_proxy;

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

/**
 * 5、创建明星代理类
 */
public class StarAgentProxy {
    /**
     * a.定义一个静态方法,为明星对象生成一个代理对象(经纪人)返回
     * @param star  用于接收一个明星对象
     * @return      返回一个代理对象
     */
    public static Skill getProxy(Star star) {
        // (1) 为明星对象,生成一个代理对象,返回
        /**
         * public static Object newProxyInstance(ClassLoader loader,
         *                                       Class<?>[] interfaces,
         *                                       InvocationHandler h)
         * 参数一:定义代理类的类加载器
         * 参数二:代理类要实现的接口列表
         * 参数三:要将方法调用分派到的调用处理程序(代理对象的核心处理程序)
         */
        return (Skill) Proxy.newProxyInstance(star.getClass().getClassLoader(),
                star.getClass().getInterfaces(), new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("经纪人收首付款~~");
                        // (2) 真正的让明星对象去唱歌、跳舞
                        /**
                         * method: 正在调用的方法对象
                         * args: 被调用的方法对象的参数
                         */
                        Object rs = method.invoke(star, args);
                        System.out.println("经纪人收尾款,把" + star.getName() + "接回来了~~");
                        return rs;    // 如果该方法有返回值,则返回结果;如果该方法无返回值,则返回null
                    }
                });
    }
}
package com.app.d10_proxy;

/**
 * 目标:学习开发出一个动态代理的对象出来,理解动态代理的执行流程。
 */
public class Test {
    public static void main(String[] args) {
        // 3、创建一个明星对象(杨超越)
        Star star = new Star("杨超越");

        // 4、杨超越没有火之前,直接调用她去唱歌、跳舞
        star.sing();
        System.out.println();
        star.jump();

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

        // 6、杨超越火了之后,想要调用她去唱歌、跳舞,必须经过她的代理对象(经纪人)
        Skill proxy = StarAgentProxy.getProxy(star);
        proxy.sing();
        System.out.println();
        proxy.jump();
    }
}
杨超越开始唱歌:我和我的祖国,一刻都不能分离~~

杨超越开始跳舞,跳的很迷人~~
---------------------------
经纪人收首付款~~
杨超越开始唱歌:我和我的祖国,一刻都不能分离~~
经纪人收尾款,把杨超越接回来了~~

经纪人收首付款~~
杨超越开始跳舞,跳的很迷人~~
经纪人收尾款,把杨超越接回来了~~

Process finished with exit code 0



(2)Java中生成代理,并指定代理做什么事

在这里插入图片描述


在这里插入图片描述



总结
  1. 代理是什么?

    • 一个对象,用来对被代理对象的行为额外做一些辅助工作
  2. 在Java中实现动态代理的步骤是?

    • 必须定义接口

    • 被代理对象需要实现这个接口

    • 使用Proxy类提供的方法,得到对象的代理对象

      在这里插入图片描述

  3. 通过代理对象调用方法,执行流程是?

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

在这里插入图片描述

  • 以后我们要学习的spring框架,它也是基于动态代理的形式返回一个更强的对象给我们使用。



二、应用案例

1、做性能分析
  • 只是模拟场景,不需要完全把功能实现出来,目的就是为了理解动态代理的优势

  • 需求:

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

    • 1、定义一个UserService表示用户业务接口,规定必须完成用户登录、删除、查询功能。
    • 2、定义一个实现类UserServiceImpl实现UserService接口,并完成相关功能,且统计每个功能的耗时。
    • 3、定义测试类,创建实现类对象,调用方法。
  • 实现:

    package com.app.d11_proxy_test2;
    
    /**
     * 1、定义用户业务接口:规定必须完成用户登录、删除、查询的功能
     */
    public interface UserService1 {
        String userLogin(String loginName, String passWord);    // 用户登录功能
        void deleteUser();  // 删除用户功能
        void selectUser();  // 查询用户功能
    }
    
    package com.app.d11_proxy_test2;
    
    /**
     * 2、定义实现类:实现用户业务接口的用户登录、删除、查询的功能
     */
    public class UserServiceImpl1 implements UserService1{
        /**
         * 实现用户登录功能
         * @param loginName     登录名
         * @param passWord      登录密码
         * @return              返回登录结果
         */
        @Override
        public String userLogin(String loginName, String passWord) {
            // 记录方法执行开始时间
            long startTime = System.currentTimeMillis();
    
            String rs = "登录名或密码错误!";
            if ("admin".equals(loginName) && "abc123".equals(passWord)) {
                System.out.println(loginName + "登录成功~~");
                try {
                    // 让该线程睡眠1秒,模拟登录耗时
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 记录方法执行结束时间
            long endTime = System.currentTimeMillis();
    
            // 输出执行此方法的耗时
            System.out.println("userLogin方法耗时:" + (endTime - startTime) / 1000.0 + "秒");
    
            return rs;  // 返回登录结果
        }
    
        @Override
        public void deleteUser() {
            // 记录方法执行开始时间
            long startTime = System.currentTimeMillis();
    
            try {
                // 让该线程睡眠2.5秒,模拟删除用户耗时
                System.out.println("正在删除用户数据.....");
                Thread.sleep(2500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            // 记录方法执行结束时间
            long endTime = System.currentTimeMillis();
    
            // 输出执行此方法的耗时
            System.out.println("deleteUser方法耗时:" + (endTime - startTime) / 1000.0 + "秒");
        }
    
        @Override
        public void selectUser() {
            // 记录方法执行开始时间
            long startTime = System.currentTimeMillis();
    
            try {
                // 让该线程睡眠3秒,模拟删除用户耗时
                System.out.println("查询了10000个用户!");
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            // 记录方法执行结束时间
            long endTime = System.currentTimeMillis();
    
            // 输出执行此方法的耗时
            System.out.println("selectUser方法耗时:" + (endTime - startTime) / 1000.0 + "秒");
        }
    }
    
    package com.app.d11_proxy_test2;
    
    /**
     * 3、定义测试类
     */
    public class Test1 {
        public static void main(String[] args) {
            // 创建实现类对象,调用方法
            UserService1 user = new UserServiceImpl1();
            user.userLogin("admin", "abc123");
            user.deleteUser();
            user.selectUser();
        }
    }
    
    admin登录成功~~
    userLogin方法耗时:1.012秒
    正在删除用户数据.....
    deleteUser方法耗时:2.502秒
    查询了10000个用户!
    selectUser方法耗时:3.009秒
    
    Process finished with exit code 0
    
    


(1)本案例存在哪些问题?
  • 业务对象的每个方法都要进行性能统计,存在大量重复的代码。
  • 如果又有新业务的功能,需要添加方法,还要写一次测试性能的代码,如果有一大堆业务呢?是不是大大降低了开发效率。


(2)使用动态代理优化本案例
  • 关键步骤:

    • 1、必须有接口,实现类要实现接口(代理通常是基于接口实现的)。

    • 3、创建一个实现类的对象,该对象为业务对象,紧接着为业务对象做一个代理对象。

      在这里插入图片描述

package com.app.d11_proxy_test2;

/**
 * 1、定义用户业务接口,规定必须完成用户登录、删除、查询功能
 */
public interface UserService2 {
    String userLogin(String loginName, String passWord);   // 用户登录
    void deleteUser();  // 用户删除
    void selectUser();   // 用户查询
}
package com.app.d11_proxy_test2;

/**
 * 2、定义实现类:实现用户业务接口
 */
public class UserServiceImpl2 implements UserService2 {
    /**
     * 实现用户登录功能
     */
    @Override
    public String userLogin(String loginName, String passWord) {
        String rs = "登录名或密码错误!";
        if ("admin".equals(loginName) && "abc123".equals(passWord)) {
            System.out.println(loginName + "登录成功~");
        }
        try {
            // 让该线程睡眠1秒,模拟登录花费时间
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return rs;
    }


    /**
     * 实现用户删除功能
     */
    @Override
    public void deleteUser() {
        try {
            // 让该线程睡眠2.5秒,模拟删除很多用户的场景
            System.out.println("正在删除用户数据...");
            Thread.sleep(2500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    /**
     * 实现用户查询功能
     */
    @Override
    public void selectUser() {
        System.out.println("查询了10000多个用户!");
        try {
            // 让该线程睡眠3秒,模拟查询很多用户的场景
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
package com.app.d11_proxy_test2;

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

/**
 * 3、定义对象代理类:负责统计每个功能的耗时
 */
public class ProxyUtil {
    /**
     * a.定义一个静态方法,为用户业务对象生成一个代理对象返回
     */
    public static UserService2 getProxy(UserServiceImpl2 userService) {
        // (1) 为对象生成一个代理对象并返回
        return (UserService2) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(), new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // (1) 记录开始时间
                        long startTime = System.currentTimeMillis();

                        // (2) 真正调用对象方法
                        Object rs = method.invoke(userService, args);

                        // (3) 记录结束时间
                        long endTime = System.currentTimeMillis();

                        // (4) 输出执行该方法的耗时
                        System.out.println(method.getName() + "方法耗时:" + (endTime - startTime) / 1000.0 + "秒");

                        // (5) 如果该方法有返回值,则返回结果;否则返回null。
                        return rs;
                    }
                });
    }
}
package com.app.d11_proxy_test2;

/**
 * 目标:掌握使用动态代理解决问题,理解使用动态代理的优势。
 */
public class Test2 {
    public static void main(String[] args) {
        // 4、创建一个用户
//        UserServiceImpl2 user = new UserServiceImpl2();

        // 5、获取一个动态代理
//        UserService2 proxy = ProxyUtil.getProxy(user);
        UserService2 proxy = ProxyUtil.getProxy(new UserServiceImpl2());

        // 6、调用方法,测试用户业务功能的性能
        proxy.userLogin("admin", "abc123");
        proxy.deleteUser();
        proxy.selectUser();
    }
}
admin登录成功~
userLogin方法耗时:1.016秒
正在删除用户数据...
deleteUser方法耗时:2.501秒
查询了10000多个用户!
selectUser方法耗时:3.002秒

Process finished with exit code 0



(3)动态代理的优势
  • 可以在不改变方法源码的情况下,实现对方法功能的增强,提高了代码的复用性。

  • 简化了编程工作,提高了开发效率,同时提高了软件系统的可扩展性。

  • 可以为被代理对象的所有方法做代理。

  • 非常的灵活,支持任意接口类型实现类对象做代理,也可以直接为接口本身做代理(后面的内容)

    在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值