• JAVA动态代理


    1 动态代理介绍、准备功能

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

    我们说杨超越的代理是中介公司派的,那中介公司怎么知道,要派一个有唱歌和跳舞功能的代理呢?

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

    接下来我们就先把有唱歌和跳舞功能的接口,和实现接口的大明星类定义出来。

    2 生成动态代理对象

    下面我们写一个为BigStar生成动态代理对象的工具类。这里需要用Java为开发者提供的一个生成代理对象的类叫Proxy类。

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

    1. public class ProxyUtil {
    2.    public static Star createProxy(BigStar bigStar){
    3.       /* newProxyInstance(ClassLoader loader,
    4.                Class[] interfaces,
    5.                InvocationHandler h)
    6.                参数1:用于指定一个类加载器
    7.                参数2:指定生成的代理长什么样子,也就是有哪些方法
    8.                参数3:用来指定生成的代理对象要干什么事情
    9.                */
    10.        // Star starProxy = ProxyUtil.createProxy(s);
    11.        // starProxy.sing("好日子") starProxy.dance()
    12.        Star starProxy = (Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
    13.                new Class[]{Star.class}, new InvocationHandler() {
    14.                    @Override // 回调方法
    15.                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    16.                        // 代理对象要做的事情,会在这里写代码
    17.                        if(method.getName().equals("sing")){
    18.                            System.out.println("准备话筒,收钱20万");
    19.                       }else if(method.getName().equals("dance")){
    20.                            System.out.println("准备场地,收钱1000万");
    21.                       }
    22.                        return method.invoke(bigStar, args);
    23.                   }
    24.               });
    25.        return starProxy;
    26.   }
    27. }

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

    1. public class Test {
    2.    public static void main(String[] args) {
    3.        BigStar s = new BigStar("杨超越");
    4.        Star starProxy = ProxyUtil.createProxy(s);
    5.        String rs = starProxy.sing("好日子");
    6.        System.out.println(rs);
    7.        starProxy.dance();
    8.   }
    9. }

    运行测试类,结果如下图所示

    3 动态代理应用

    现有如下代码

    1. /**
    2. * 用户业务接口
    3. */
    4. public interface UserService {
    5.    // 登录功能
    6.    void login(String loginName,String passWord) throws Exception;
    7.    // 删除用户
    8.    void deleteUsers() throws Exception;
    9.    // 查询用户,返回数组的形式。
    10.    String[] selectUsers() throws Exception;
    11. }

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

    1. /**
    2. * 用户业务实现类(面向接口编程)
    3. */
    4. public class UserServiceImpl implements UserService{
    5.    @Override
    6.    public void login(String loginName, String passWord) throws Exception {
    7.        long time1 = System.currentTimeMillis();
    8.        if("admin".equals(loginName) && "123456".equals(passWord)){
    9.            System.out.println("您登录成功,欢迎光临本系统~");
    10.       }else {
    11.            System.out.println("您登录失败,用户名或密码错误~");
    12.       }
    13.        Thread.sleep(1000);
    14.        long time2 = System.currentTimeMillis();
    15.        System.out.println("login方法耗时:"+(time2-time1));
    16.   }
    17.    @Override
    18.    public void deleteUsers() throws Exception{
    19.        long time1 = System.currentTimeMillis();
    20.        System.out.println("成功删除了1万个用户~");
    21.        Thread.sleep(1500);
    22.        long time2 = System.currentTimeMillis();
    23.        System.out.println("deleteUsers方法耗时:"+(time2-time1));
    24.   }
    25.    @Override
    26.    public String[] selectUsers() throws Exception{
    27. long time1 = System.currentTimeMillis();
    28.        System.out.println("查询出了3个用户");
    29.        String[] names = {"张全蛋", "李二狗", "牛爱花"};
    30.        Thread.sleep(500);
    31. long time2 = System.currentTimeMillis();
    32.        System.out.println("selectUsers方法耗时:"+(time2-time1));
    33.        return names;
    34.   }
    35. }

    观察上面代码发现有什么问题吗?

    我们会发现每一个方法中计算耗时的代码都是重复的,我们可是学习了动态代理的高级程序员,怎么能忍受在每个方法中写重复代码呢!况且这些重复的代码并不属于UserSerivce的主要业务代码。

    所以接下来我们打算,把计算每一个方法的耗时操作,交给代理对象来做。

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

    1. /**
    2. * 用户业务实现类(面向接口编程)
    3. */
    4. public class UserServiceImpl implements UserService{
    5.    @Override
    6.    public void login(String loginName, String passWord) throws Exception {
    7.        if("admin".equals(loginName) && "123456".equals(passWord)){
    8.            System.out.println("您登录成功,欢迎光临本系统~");
    9.       }else {
    10.            System.out.println("您登录失败,用户名或密码错误~");
    11.       }
    12.        Thread.sleep(1000);
    13.   }
    14.    @Override
    15.    public void deleteUsers() throws Exception{
    16.        System.out.println("成功删除了1万个用户~");
    17.        Thread.sleep(1500);
    18.   }
    19.    @Override
    20.    public String[] selectUsers() throws Exception{
    21.        System.out.println("查询出了3个用户");
    22.        String[] names = {"张全蛋", "李二狗", "牛爱花"};
    23.        Thread.sleep(500);
    24.        return names;
    25.   }
    26. }

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

    1. public class ProxyUtil {
    2.    public static UserService createProxy(UserService userService){
    3.        UserService userServiceProxy
    4.            = (UserService) Proxy.newProxyInstance(
    5.            ProxyUtil.class.getClassLoader(),
    6.            new Class[]{UserService.class},
    7.            new InvocationHandler() {
    8.                                                                           @Override
    9.            public Object invoke(                                                                             Object proxy,
    10.                              Method method,
    11.                                  Object[] args) throws Throwable {                             if(
    12.                    method.getName().equals("login") ||                                             method.getName().equals("deleteUsers")||
    13.                    method.getName().equals("selectUsers")){
    14.                    //方法运行前记录毫秒值        
    15.                    long startTime = System.currentTimeMillis();
    16.                    //执行方法
    17.                    Object rs = method.invoke(userService, args);
    18.                    //执行方法后记录毫秒值
    19.                    long endTime = System.currentTimeMillis();
    20.                    System.out.println(method.getName() + "方法执行耗时:" + (endTime - startTime)/ 1000.0 + "s");
    21.                    return rs;
    22.               }else {
    23.                    Object rs = method.invoke(userService, args);
    24.                    return rs;                                                               }
    25.           }                                                                 });
    26.        //返回代理对象
    27.        return userServiceProxy;
    28.   }
    29. }

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

    1. /**
    2. * 目标:使用动态代理解决实际问题,并掌握使用代理的好处。
    3. */
    4. public class Test {
    5.    public static void main(String[] args) throws Exception{
    6.        // 1、创建用户业务对象。
    7.        UserService userService = ProxyUtil.createProxy(new UserServiceImpl());
    8.        // 2、调用用户业务的功能。
    9.        userService.login("admin", "123456");
    10.        System.out.println("----------------------------------");
    11.        userService.deleteUsers();
    12.        System.out.println("----------------------------------");
    13.        String[] names = userService.selectUsers();
    14.        System.out.println("查询到的用户是:" + Arrays.toString(names));
    15.        System.out.println("----------------------------------");
    16.   }
    17. }

    执行结果如下图所示

    动态代理对象的执行流程如下图所示,每次用代理对象调用方法时,都会执行InvocationHandler中的invoke方法。

  • 相关阅读:
    2023年亚太杯数学建模思路 - 案例:最短时间生产计划安排
    vue 中 style 标签中的 scoped 属性(作用域)和 lang 属性的介绍
    【全栈】vue3.0 + golang 尝试前后端分离【博客系统1.1】有进展了
    使用Redux处理异步
    【学习笔记】[AGC064C] Erase and Divide Game
    高通WLAN框架学习(32)-- WLAN/P2P set IOCTLs(iwpriv)命令大全
    爬虫过程和反爬
    嵌入式基础准备 | Linux命令(包括 文件、目录和压缩、系统操作、vi、vim、ctags、cscope)
    Vue中的ajax请求
    哈希的应用 位图+布隆过滤器+海量数据处理
  • 原文地址:https://blog.csdn.net/weixin_68522070/article/details/136418302