• 12.反射与动态代理


    使用代理可以在程序运行时创建一个实现指定接口的新类(代理类)。
    通常只有在编译时无法确定需要使用哪个接口时才需要使用代理,这对于应用程序员很少见。
    但是对于系统程序员而言,代理可以为工具类提供更加灵活的特性。

    下面我们学习一个简单的销售场景来理解动态代理的精髓。

    1.房屋销售应用程序设计

    首先我们定义一个销售者接口:
    这个接口可以被很多销售者实现,比如时装销售者,房屋销售者等,它是一个公共的销售者接口。

    public interface Seller {
        // 销售方法
        void sell();
        void sayHello();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    然后我们要定义一个房屋销售者实现它:

    package ReflectStudy.Exa12.noProxy;
    
    public class HouseSeller implements Seller {
        public void sell() {
            // 实现接口的方法,用输出来区别该类
            System.out.println("销售人员在卖房子");
        }
    
        @Override
        public void sayHello() {
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    好了,现在有了房屋销售者,我们可以编写程序对其进行使用了:

    import java.lang.reflect.Proxy;
    public class Test {
        public static void main(String[] args) {
            Seller seller ;
            seller= new HouseSeller();
            System.out.println("不使用代理方式:");
            //普通方式调用sell()方法
            seller.sell();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    这样做虽然可以实现功能,但是,如果我们想要让系统不停止运行的情况下去改变销售者的逻辑几乎是不可能的,因为代码里写死了!!

    public class HouseSeller implements Seller {
        public void sell() {
            // 实现接口的方法,用输出来区别该类
            System.out.println("销售人员在卖房子");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    但是,通过代理的方式,我们可以动态的生成代理类,然后使用代理类完成我们想要的功能。

    2.JDK动态代理实例

    在JDK中的动态代理是基于接口实现的,因为Proxy已经使用了继承(动态生成的代理类都必须继承Proxy)

    使用JDK动态代理的步骤如下:

    (1)首先定义我们想要代理的接口

    package ReflectStudy.Exa12.noProxy;
    
    public interface Seller {
        // 简单的测试方法
        void sell();
        //
        void sayHello();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    (2)然后编写一个类实现InvocationHandler接口

    实际上我们生成的代理类执行方法是根据这里的invoke的逻辑来执行的。

    package ReflectStudy.Exa12.dymProxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class Agency implements InvocationHandler {
    
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println(method.getName());
            if(method.getName().equals("sell"))
                System.out.println("代理人员在卖房子");
            // 用来处理代理类
            else if(method.getName().equals("sayHello"))
                System.out.println("代理人员像你打招呼");
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    (3)最后使用Proxy.newProxyInstance方法动态生成代理类

    package ReflectStudy.Exa12.dymProxy;
    
    import ReflectStudy.Exa12.noProxy.Seller;
    
    import java.lang.reflect.Proxy;
    
    public class Test {
        public static void main(String[] args) {
            Seller seller ;
            System.out.println("使用代理方式:");
            // 获得Seller类的类加载器
            ClassLoader loader = Seller.class.getClassLoader();
            //动态生成代理类
            seller = (Seller) Proxy.newProxyInstance(loader, new Class[] { Seller.class }, new Agency());
            // 代理方式调用sell()方法
            seller.sell();
            seller.sayHello();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述

    3.JDK动态代理是如何工作的?

    首先我们将上面的使用JDK代理的程序的UML设计画出来,然后解析一下程序运行的详细流程
    在这里插入图片描述

    我们可以看出,JDK动态代理的元素主要有:被代理的接口Seller,实现InvocationHandler的类Agency,Proxy类。

    其中Proxy的newProxyInstance方法根据被代理的接口和实现InvocationHandler类来生成代理类。

    生成完代理类后,当我们执行被代理的方法时,其实程序会进入Agency的invoke里进行执行,
    实际上,它就是执行这个方法。

    public Object invoke(Object proxy, Method method, Object[] args)
    
    • 1

    我们看一下这个方法的参数,proxy其实是生成的代理类,method是需要执行的方法的名称,args[]是需要执行方法的参数。我们就在这里来是实现代理方法。

    用起来我们好像都理解了,但是里面到底是怎么实现的呢? 关键就在于Proxy的newProxyInstance方法,这个方法生成的代理类到底是怎样的呢? 如果我们能看到,是不是就能理解了呢?

    3.1 将生成的代理类可视化

    首先我们需要编写一个方法:
    这个方法用于将生成的代理类信息写到文件中:

    /**
     * 将根据类信息动态生成的二进制字节码保存到硬盘中,默认的是clazz目录下
     * params: clazz 需要生成动态代理类的类
     * proxyName: 为动态生成的代理类的名称
     */
    public static void generateClassFile(Class<?> clazz, String proxyName) {
        // 根据类信息和提供的代理类名称,生成字节码
        byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
       // String paths = clazz.getResource(".").getPath();
       // System.out.println(paths);
        FileOutputStream out = null;
        try {
            //保留到硬盘中
            out = new FileOutputStream( proxyName + ".class");
            out.write(classFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    然后再客户方法里使用即可:

    public static void main(String[] args) {
        Seller seller ;
        System.out.println("使用代理方式:");
        // 获得Seller类的类加载器
        ClassLoader loader = Seller.class.getClassLoader();
        //动态生成代理类
        seller = (Seller) Proxy.newProxyInstance(loader, new Class[] { Seller.class }, new Agency());
        // 代理方式调用sell()方法
        seller.sell();
        seller.sayHello();
    
        generateClassFile(seller.getClass(), "SellerProxy");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    运行之,我们可以看到,生成的代理类是长这样子的:
    在这里插入图片描述
    我们可以看到,代理类继承的Proxy,实现了Seller.

    我们主要截取代理方法内部逻辑来看看:

    public final void sayHello() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    我们可以看到,实际上它就是将 一些变量作为参数来执行invoke的!!

    我们知道,this就是代理类本身,那么m3呢?请看代理类最下面的代码:

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("ReflectStudy.Exa12.noProxy.Seller").getMethod("sayHello");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("ReflectStudy.Exa12.noProxy.Seller").getMethod("sell");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这里的m3其实就在生成代理类的时候就已经定义好了。这里就是用反射来生成对应的Method对象来进行使用的。

    项目代码:
    https://gitee.com/yan-jiadou/study/tree/master/Java%E5%8A%A8%E6%89%8B%E5%81%9A%E4%B8%80%E5%81%9A/src/main/java/ReflectStudy

  • 相关阅读:
    计算机保研英语常见问题
    java spring cloud 企业电子招标采购系统源码:营造全面规范安全的电子招投标环境,促进招投标市场健康可持续发展
    BEM命名法
    Flutter 数据存储--shared_preferences使用详情
    PPT NO.2 ​插入透明校徽
    Vue 下载本地文件夹和图片动态引入(解决无法从网站上提取文件)
    微信小程序 - 调用微信 API 回调函数内拿不到 this 问题(解决方案)
    【Android】设置全局标题栏
    超详细全面 spring 复习总结笔记
    一、领域驱动设计核心思想与设计过程
  • 原文地址:https://blog.csdn.net/c1776167012/article/details/125608724