有关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();
从传统方式中我们可以看出,我们的XiaoMi
、DaiEr
这两个类虽然可以实现我们的需求,但是它的代码很冗余。为了解决这些类似的问题,我们可以使用动态代理
的方式:
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;
}
}
我们代理类调用了InvocationHandlerImpl的invoke()
。而在这个方法的内部可以看到我们的方法实现了增强切入,而且也可以看到我们这里通过反射调用了被代理对象的原方法(未增强的)。我们可以看到InvocationHandlerImpl
的Invoke()
被是可以返回一个对象的,而这个对象是通过反射调用方法的返回值。
到这里我们的动态代理的底层机制就讲完了。我们将他们的UML类图列出来,方便大家的理解(讲述中没涉及到的方法或者属性等都没列出来):
五、总结
以上就是我们引入篇的所有内容。我们这里再理一下动态代理的思路:①获取代理类→②代理类调用方法→③代理类中调用父类的
invocationHandler(实现类)
属性的invoke()
→④在invocationHandler
的invoke()
中实现方法的增强,并且通过反射调用了原方法(被代理类的原方法)→⑤完成增强切入。
如果文章中有描述不准确或者错误的地方,还望指正。您可以留言📫或者私信我。🙏
最后希望大家多多 关注+点赞+收藏^_^,你们的鼓励是我不断前进的动力!!!
感谢感谢~~~🙏🙏🙏