• 【Spring(五)】引入篇:一文带你弄懂AOP的底层原理(动态代理)


    有关Spring的所有文章都收录于我的专栏:👉Spring👈
    目录
    一、前言
    二、使用AOP需要的依赖
    三、引入
    四、AOP的底层原理之动态代理
    五、总结


    相关文章

    【Spring(一)】如何获取对象(Bean)【Spring(一)】如何获取对象(Bean)
    【Spring(二)】java对象属性的配置(Bean的配置)【Spring(二)】java对象属性的配置(Bean的配置)
    【Spring(三)】熟练掌握Spring的使用【Spring(三)】熟练掌握Spring的使用
    【Spring(四)】Spring基于注解的配置方式【Spring(四)】Spring基于注解的配置方式

    一、前言

     在前面的四节内容当中,我们对Spring的IOC做了很详细的阐述。接下来我们开始了解,Spring的另一大特色功能AOP。
     AOP(Aspect Oriented Programming)面向切面编程。之所以会有这个名称,是因为它的功能就像我们在汉堡中加入鸡排一样,会将我们的新增的业务功能(鸡排)加入到需要添加的类(汉堡)中去。

    二、使用AOP需要的依赖

    在这里插入图片描述
    里边有几个jar包不是spring自带的,需要自行下载。

    三、引入

     在正式开始之前,我们先引出一个场景:当我们使用一个小米电脑工作的时候,它就会有三种状态:工作之前的开机,正在处于运行状态,工作完之后关机。那当我们使用其他品牌电脑的时候,也都是同样的流程,只不过我们在运行状态的时候,电脑不同而已。现在我们需要执行下列的语句:

    开机
    小米电脑运行
    关机
    -------------------------
    开机
    戴尔电脑运行
    关机
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    思考一下,我们传统的方式应该怎么做。

    public interface Computer{
    	public void run();
    }
    
    • 1
    • 2
    • 3
    public class XiaoMi implements Computer {
        @Override
        public void run() {
        System.out.println("开机");
        System.out.println("小米电脑运行");
        System.out.println("关机");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    public class DaiEr implements Computer {
        @Override
        public void run() {
        System.out.println("开机");
        System.out.println("戴尔电脑运行");
        System.out.println("关机");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    测试类

    Computer computer = new XiaoMi();
    computer.run();
    Computer computer1 = new DaiEr();
    computer1.run();
    
    • 1
    • 2
    • 3
    • 4

    从传统方式中我们可以看出,我们的XiaoMiDaiEr这两个类虽然可以实现我们的需求,但是它的代码很冗余。为了解决这些类似的问题,我们可以使用动态代理的方式:


    public interface Computer{
    	public void run();
    }
    
    • 1
    • 2
    • 3
    public class XiaoMi implements Computer {
        @Override
        public void run() {
        System.out.println("小米电脑运行");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    InvocationHandler实现类

    package com.jl.spring.proxy4;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class InvocationHandlerImpl implements InvocationHandler {
        private Object object;
    
        public InvocationHandlerImpl() {
        }
    
        public InvocationHandlerImpl(Object object) {
            this.object = object;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("开机");
            method.invoke(object, args);
            System.out.println("关机");
            return null;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    测试类

    package com.jl.spring.proxy4;
    
    import sun.misc.ProxyGenerator;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.lang.reflect.Proxy;
    
    public class Proxy_Invocation_Test {
        public static void main(String[] args) {
            Computer xiaoMi = new XiaoMi();
            InvocationHandlerImpl invocationHandler = new InvocationHandlerImpl(xiaoMi);
            Computer computer =
                    (Computer)Proxy.newProxyInstance(XiaoMi.class.getClassLoader(), XiaoMi.class.getInterfaces(), invocationHandler);
            computer.run();
    //        saveProxyClass(Computer.class);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    运行结果:
    在这里插入图片描述
    到这里同学们可能是一脸的懵逼。这个为什么要有个nvocationHandler的实现类,还有测试类中的Proxy.newProxyInstance()等都是用来干啥的。我在刚开始学的时候,也很懵。但仔细研究之后才发现其中的秘密。下边我们就开始讲解动态代理的底层。

    四、AOP的底层原理之动态代理

    我们讲解动态代理还是以上边的Computer为例来讲述。
    动态代理顾名思义:就是别人代替你去干某些事情。那么想要我们的类被代理,就得首先获得一个代理类,而在Java中我们可以使用Proxy.newProxyInstance()获取到代理类。现在我们有了代理类被代理类,想要让他两个产生关联就得通过Proxy.newProxyInstance()的参数。即我们将被代理类的接口信息类加载器传进去,就会得到该类的代理类。也就是上边例子中的句代码:

    Computer computer =(Computer)Proxy.newProxyInstance(XiaoMi.class.getClassLoader(), XiaoMi.class.getInterfaces(), invocationHandler);
    
    • 1

    至于这里的第三个参数invocationHandler,我们放到后边讲。
    很好!到这里,我们已经得到了我们Computer的代理类。接下来我们顺理成章的调用computer.run()。那么问题又来了,我们的XiaoMi(实现了Computer接口),调用它的run()为什么就会执行得到我们增强之后的方法呢❓
    别急,我们这里将它的信息打印出来:

    computer的运行类型 = class com.sun.proxy.$Proxy0
    
    • 1

    看到这个信息之后,是不是很懵逼。我们写的类当中,没有这个名为$Proxy0的类啊。但它的运行类型为啥是这个。聪明的小伙伴肯定会想到:这个我们不是调用了Proxy.newProxyInstance()嘛,它会得到一个代理类。没错,答案也正是如此!这个$Proxy0代理类就是我们调用方法得到的。到这里好似还没解决我们最开始的问题:增强之后的方法是如何执行到的
    至于这个疑问,我们就得看看$Proxy0代理类中的信息。但在我们运行的时候,他不会产生相应的Java类,我们想要查看该代理类的相关信息,就得将它的字节码用IO方式输出。如下:

    // 保存字节码方法
    private static void saveProxyClass(Class clazz){
        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{clazz});
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(new File("绝对路径\\$proxy0.class"));
            fileOutputStream.write(bytes);
    
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (fileOutputStream!=null){
                try {
                    fileOutputStream.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

    我们打开生成的字节码文件看一下(这里是IDEA反编译之后的):

    //
    // Source code recreated from a .class file by IntelliJ IDEA
    // (powered by Fernflower decompiler)
    //
    
    import com.jl.spring.proxy4.Computer;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    
    public final class $Proxy0 extends Proxy implements Computer {
        private static Method m1;
        private static Method m3;
        private static Method m2;
        private static Method m0;
    
        public $Proxy0(InvocationHandler var1) throws  {
            super(var1);
        }
    
        public final boolean equals(Object var1) throws  {
            try {
                return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final void run() throws  {
            try {
                super.h.invoke(this, m3, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final String toString() throws  {
            try {
                return (String)super.h.invoke(this, m2, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final int hashCode() throws  {
            try {
                return (Integer)super.h.invoke(this, m0, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m3 = Class.forName("com.jl.spring.proxy4.Computer").getMethod("run");
                m2 = Class.forName("java.lang.Object").getMethod("toString");
                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
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

     看到这个类之后,有没有一种豁然开朗的感觉。没有也没关系😳,我来解释一下。从代码可以看出,代理类继承了Proxy同时实现了Computer接口;静态代码块获取到了方法对应的Method对象,而这些方法都是我们Computer中的方法,其中我们最想看到的就是run;代理类中的方法名也都和Computer接口中的方法名一致(因为实现嘛);我们主要来分析一下run()的代码,其余方法都类似:

    public final void run() 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

    从这里我们看到,run方法内部调用了父类的h属性(InvocationHandler对象)invoke()。到这里我们就得到我们实现的InvocationHandlerImpl中去看一下:

    public class InvocationHandlerImpl implements InvocationHandler {
        private Object object;
    
        public InvocationHandlerImpl(Object object) {
            this.object = object;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("开机");
            method.invoke(object, args);
            System.out.println("关机");
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    我们代理类调用了InvocationHandlerImplinvoke()。而在这个方法的内部可以看到我们的方法实现了增强切入,而且也可以看到我们这里通过反射调用了被代理对象的原方法(未增强的)。我们可以看到InvocationHandlerImplInvoke()被是可以返回一个对象的,而这个对象是通过反射调用方法的返回值。


     到这里我们的动态代理的底层机制就讲完了。我们将他们的UML类图列出来,方便大家的理解(讲述中没涉及到的方法或者属性等都没列出来):
    在这里插入图片描述

    五、总结

     以上就是我们引入篇的所有内容。我们这里再理一下动态代理的思路:①获取代理类→②代理类调用方法→③代理类中调用父类的invocationHandler(实现类)属性的invoke()→④在invocationHandlerinvoke()中实现方法的增强,并且通过反射调用了原方法(被代理类的原方法)→⑤完成增强切入。

    如果文章中有描述不准确或者错误的地方,还望指正。您可以留言📫或者私信我。🙏
    最后希望大家多多 关注+点赞+收藏^_^,你们的鼓励是我不断前进的动力!!!
    感谢感谢~~~🙏🙏🙏

  • 相关阅读:
    @Pointcut 使用
    前端使用 Konva 实现可视化设计器(8)- 预览框
    低代码平台审批表单设计--异行星低代码平台为例(二)
    Postgres数据库使用any和all判断数组解决IN和NOT IN条件参数超限的问题
    vue优化之如何管理系统变量
    Linux下gdb调试- awatch 命令设置读观察点
    驱动开发-按键中断
    NativeScaler()与loss_scaler【loss.backward()和 optimizer.step()】
    华为数字化转型之道 认知篇 第二章 数字化转型框架
    设计模式一: Observer(观察者模式)
  • 原文地址:https://blog.csdn.net/qq_35947021/article/details/127965320