【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自带的,需要自行下载。

三、引入

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

开机
小米电脑运行
关机
-------------------------
开机
戴尔电脑运行
关机

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

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

测试类

Computer computer = new XiaoMi();
computer.run();
Computer computer1 = new DaiEr();
computer1.run();

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


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

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;
    }
}

测试类

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);
    }
}

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

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

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

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

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

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

看到这个信息之后,是不是很懵逼。我们写的类当中,没有这个名为$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();
            }
        }
    }
}

我们打开生成的字节码文件看一下(这里是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());
        }
    }
}

 看到这个类之后,有没有一种豁然开朗的感觉。没有也没关系😳,我来解释一下。从代码可以看出,代理类继承了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);
    }
}

从这里我们看到,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;
    }
}

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


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

五、总结

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

艺术留白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值