Spring AOP 底层实现原理

引言:

本人之前对于AOP的理解知之甚少,但是却比较好奇其中的原理,所以就花时间学习了一下,接下里就给大家介绍一下。
本篇会详细介绍AOP增强的各个功能实现原理,但并不涉及源码解读,只是对每个功能实现原理进行讲解,内容有点长,耐心看完一定会有收获。
加油!!!


AOP 增强

proxy 代理增强

jdk动态代理

jdk 动态代理的主要原理是根据接口来创建代理。创建出来的代理类和目标类是实现了同一个接口的,然后对接口的方法,相较于目标类来说进行了增强。

所以这里也可以总结出来:

  • 目标类和代理类是实现了相同的接口(两个的关系比作兄弟)
  • 不用管目标类的方法和类是否被final修饰,代理类都可以实现代理(这跟后面的 cglib 实现是不一样的)

接下来调用一下 jdk 的动态代理:

interface Foo{
    void foo();
}

static class Target implements Foo{
    @Override
    public void foo() {
        System.out.println("target foo");
    }
}

public static void main(String[] args) {
    Target target = new Target();
    ClassLoader classLoader = JdkProxyDemo.class.getClassLoader();
    Foo proxy = (Foo) Proxy.newProxyInstance(classLoader, new Class[]{Foo.class}, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("before");
            Object result = method.invoke(target, args);
            System.out.println("after");
            return result;
        }
    });

    proxy.foo();
}

jdk 的动态代理主要通过newProxyInstance方法用来创建代理:

参数一ClassLoader:类加载器,因为不管是 jdk 还是 cglib 实现的代理都是在类加载的阶段创建出来的代理(没有源码),所以需要一个类加载器。

参数二Class[]:实现的接口集合,因为 jdk 是通过接口来实现的代理,所以需要几口集合。

参数三InvocationHandler:代表对代理类的约束,规定了代理类需要怎么执行方法,所以创建一个匿名内被类让开发者自己写如何实现代理。

  • invoke 方法的三个参数:
    • proxy:代理对象
    • method:目标方法
    • args:方法参数

想要执行目标方法,就可以在InvocationHandler中调用method反射调用目标方法,我们外部需要创建一个目标类,进行反射。

最后返回的对象,由于代理也实现了接口所以可以直接强转成接口类。

原理
这里我进行模拟jdk是怎么实现动态代理的,是怎么设计的。

首先准备一个接口和类

interface Foo{
    void foo();
}

static class Target implements Foo{

    @Override
    public void foo() {
        System.out.println("target foo");
    }
}

public static void main(String[] args) {

}

然后我们模仿jdk动态代理的样子创建一个代理类$Proxy0,实现接口。

public class $Proxy0 implements Foo {
    @Override
    public void foo() {
        // 方法增强

        // 调用目标方法
    }
}

我们实现的接口目的就只有两个:方法增强调用目标方法

所以我们可以简单的实现一下:

public class $Proxy0 implements Foo {
    @Override
    public void foo() {
        // 方法增强
        System.out.println("before...");
        // 调用目标方法
        new Target().foo();
    }
}

进行调用查看一下:

public static void main(String[] args) {
    Foo proxy = new $Proxy0();
    proxy.foo();
}

因为实现了Foo接口,所以直接转化为接口类型了

before...
target foo

我们发现就实现了代理,但是这里面就有个问题:

这个增强的逻辑被我们写死了,只能这样增强,但是事实上增强的逻辑千变万化,无穷无尽我们就这样写死了就只对应的一种方法,而且有的时候我们不一定会调用目标,情况很多变。

所以这里我们应该把这部分抽象出来,不应该直接在方法内部写死。

我们在创建一个接口invocationHandler

interface InvocationHandler{
    void invoke();
}

然后让代理类在内部创建这个对象

并且通过构造函数进行初始化,然后让代理方法进行调用,而具体实现什么样的增强逻辑,在创建对象时让开发者自己填入进来。

代理对象变成这样:

public class $Proxy0 implements Foo {

    private InvocationHandler handler;

    public $Proxy0(InvocationHandler handler) {
        this.handler = handler;
    }

    @Override
    public void foo() {
        handler.invoke();
    }
}

然后我们创建对象时,new一个匿名内部类,然后填写自己的逻辑。

public static void main(String[] args) {
    Foo proxy = new $Proxy0(new InvocationHandler() {
        @Override
        public void invoke() {
            // 方法增强
            System.out.println("before...");
            // 调用目标方法
            new Target().foo();
        }
    });
    proxy.foo();
}

最后调用一下:

被增强了

before...
target foo

这样就成功把要处理的增强逻辑抽出来让对应的开发者去写,而代理类就不需要考虑那么多情况了,只需要创建代理即可。

但是这样并没有完,还是会存在问题:

这时如果我们的Foo接口再创建一个bar方法看看会出现什么样的情况:

// 接口
interface Foo{
    void foo();

    void bar();
}

interface InvocationHandler{
    void invoke();
}

// 目标类
static class Target implements Foo{

    @Override
    public void foo() {
        System.out.println("target foo");
    }

    @Override
    public void bar() {
        System.out.println("target bar");
    }
}

// 代理类
public class $Proxy0 implements Foo {

    private InvocationHandler handler;

    public $Proxy0(InvocationHandler handler) {
        this.handler = handler;
    }

    @Override
    public void foo() {
        handler.invoke();
    }

    @Override
    public void bar() {
        handler.invoke();
    }
}

我们调用一下bar方法:

// 调用方法
proxy.bar();

// 结果
before...
target foo
before...
target foo

我们发现虽然被增强了,但是被增强的还是foo方法,这是因为我们在invoke方法里面写的逻辑就是只有增强foo方法,这也是写死的。所以我们为了解决这个问题,我们应该通过参数把方法和方法参数给传进来,这样invoke的方法就不需要考虑具体怎么区分和调用方法了,只需要对方法的内容增强即可。

我们给invoke方法添加两个参数:

然后代码修改为这样:

// 抽象方法
interface InvocationHandler{
    void invoke(Method method, Object[] args) throws Throwable;
}


// 代理类
public class $Proxy0 implements Foo {

    private InvocationHandler handler;

    public $Proxy0(InvocationHandler handler) {
        this.handler = handler;
    }

    @Override
    public void foo() {

        try {
            Method foo = Foo.class.getMethod("foo");
            handler.invoke(foo, new Object[0]);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    @Override
    public void bar() {
        try {
            Method bar = Foo.class.getMethod("bar");
            handler.invoke(bar, new Object[0]);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

// 创建代理
Foo proxy = new $Proxy0(new InvocationHandler() {
    @Override
    public void invoke(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        // 方法增强
        System.out.println("before...");
        // 调用目标方法
        method.invoke(new Target(), args);
    }
});

最后我们在调用一下:

// 调用方法
proxy.foo();
proxy.bar();

// 结果
before...
target foo
before...
target bar

我们可以发现,两个方法都被增强了。

接下来就是处理返回值,有可能我们的方法还存在着返回值,所以我们需要进行处理。

例如我们给bar方法增加一个返回值:

// 接口
interface Foo{
    void foo();

    int bar();
}

// 目标类
static class Target implements Foo{

    @Override
    public void foo() {
        System.out.println("target foo");
    }

    @Override
    public int bar() {
        System.out.println("target bar");
        return 100;
    }
}

这时代理类也需要添加返回值。

但是代理类调用的invoke方法,所以应该给invoke方法添加返回值,但是每个方法都是调用的invoke方法,所以我们应该给invoke方法添加一个Object方法的返回值,这样每个方法都可以使用这个。

// InvocationHandler
interface InvocationHandler{
    Object invoke(Method method, Object[] args) throws Throwable;
}

// 生成代理
Foo proxy = new $Proxy0(new InvocationHandler() {
    @Override
    public Object invoke(Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        // 方法增强
        System.out.println("before...");
        // 调用目标方法
        return method.invoke(new Target(), args);
    }
});

// 代理类的方法
public int bar() {
    try {
        Method bar = Foo.class.getMethod("bar");
        return (int)handler.invoke(bar, new Object[0]);
    } catch (Throwable e) {
        e.printStackTrace();
    }
}

这样就完成了返回值的处理。

但是bar方法正常情况下有返回值,异常情况下却没有返回值,所以我们需要处理一下异常。

jdk动态代理在Spring底层分为异常进行处理,一种是运行时异常,一种是检查时异常。

@Override
public void foo() {
    try {
        Method foo = Foo.class.getMethod("foo");
        handler.invoke(foo, new Object[0]);
    } catch (RuntimeException | Error e){
        throw e;
    }catch (Throwable e) {
        throw new UndeclaredThrowableException(e);
    }
}

@Override
public int bar() {
    try {
        Method bar = Foo.class.getMethod("bar");
        return (int)handler.invoke(bar, new Object[0]);
    }catch (RuntimeException | Error e){
        throw e;
    }catch (Throwable e) {
        throw new UndeclaredThrowableException(e);
    }
}

还有问题,就是我们现在每次获取方法都是在调用时才进行获取的,每次调用都获取一次,我们可以先获取好然后每次调用的时候直接用就好了。

static Method bar;
static Method foo;

static {
    try {
        bar = Foo.class.getMethod("bar");
        foo = Foo.class.getMethod("foo");
    } catch (NoSuchMethodException e) {
        throw new NoSuchMethodError(e.getMessage());
    }
}

其实这里还缺一些东西,在源码中还有一个代理类的参数proxy,我们也来加一下,然后再调用的时候,因为就是在代理类中调用,所以直接传this即可。

interface InvocationHandler{
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}


@Override
public void foo() {
    try {
        handler.invoke(this, foo, new Object[0]);
    } catch (RuntimeException | Error e){
        throw e;
    }catch (Throwable e) {
        throw new UndeclaredThrowableException(e);
    }
}

@Override
public int bar() {
    try {
        return (int)handler.invoke(this, bar, new Object[0]);
    }catch (RuntimeException | Error e){
        throw e;
    }catch (Throwable e) {
        throw new UndeclaredThrowableException(e);
    }
}

最后在源码中其实代理类继承了一个Proxy父类,然后父类里面维护了一个InvocationHandler和构造方法,所以我们直接继承Proxy即可。

public class $Proxy0 extends Proxy implements Foo {


    public $Proxy0(InvocationHandler handler) {
        super(handler);
    }

    static Method bar;
    static Method foo;

    static {
        try {
            bar = Foo.class.getMethod("bar");
            foo = Foo.class.getMethod("foo");
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }

    @Override
    public void foo() {

        try {
            this.h.invoke(this, foo, new Object[0]);
        } catch (RuntimeException | Error e){
            throw e;
        }catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public int bar() {
        try {
            return (int)this.h.invoke(this, bar, new Object[0]);
        }catch (RuntimeException | Error e){
            throw e;
        }catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }
}

最后调用:

before...
target foo
before...
target bar

正常调用。

以上就是模拟源码写的代码。

cglib动态代理

cglib 的动态代理,底层是通过继承父类来实现的,所以代理类相当于是目标类的子类,然后代理类通过重写父类的方法进行对方法的增强。

所以从这里我们可以总结出来:

  • cglib 代理是通过继承父类实现的(两者的关系可以比作父子)
  • 当目标类或目标方法被final修饰之后,就无法进行增强。

接下来我们调用一下cglib动态代理

static class Target{
    public void foo(){
        System.out.println("target foo");
    }
}

public static void main(String[] args) {
    Target target = new Target();
    Target proxy = (Target) Enhancer.create(Target.class, new MethodInterceptor() {
        @Override
        public Object intercept(Object p, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            System.out.println("before");
            Object result = method.invoke(target, args);
            
            //                Object o = methodProxy.invokeSuper(p, args);
            System.out.println("after");
            return result;
        }
    });

    proxy.foo();
}

cglib 的动态代理是通过Enhancercreate方法进行创建的:

第一个参数target:目标类的class对象

第二个参数MethodInterceptor:与InvocationHandler的作用相近,可以理解为就是来开发者自己限制方法是如何被调用(增强)。

  • intercept的方法参数
    • p:代理类
    • method:目标方法
    • args:方法参数
    • MethodProxy:后面进行介绍(可以用来执行目标方法)

想要执行目标方法这里一共有三个方法可以使用:

  • 通过反射调用(效率比较低)
  • 通过MethodProxyinvoke方法直接正向调用目标类的方法(需要目标类参数)
  • 通过MethodProxyinvokeSuper方法直接正向调用目标类的方法(需要代理类参数)

最后的返回类,由于是子类所以也可以直接强转成父类进行调用。

原理
接下来我们也像jdk一样模拟一下源码的调用。

这次我们就直接一点,其实cglib和jdk差不多,只不过cglib里面维护了一个MethodInterceptor。然后通过继承父类重写方法即可。

目标类

public class Target {

    public void save(){
        System.out.println("save()");
    }

    public void save(int i){
        System.out.println("save(int i)");
    }

    public void save(long i){
        System.out.println("save(long i)");
    }
}

代理类

public class Proxy extends Target{
    private MethodInterceptor methodInterceptor;

    public void setMethodInterceptor(MethodInterceptor methodInterceptor) {
        this.methodInterceptor = methodInterceptor;
    }

    static Method save0;
    static Method save1;
    static Method save2;
    static{
        try {
            save0 = Target.class.getMethod("save");
            save1 = Target.class.getMethod("save", int.class);
            save2 = Target.class.getMethod("save", long.class);
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }

    @Override
    public void save() {
        try {
            methodInterceptor.intercept(this, save0, new Object[0], null);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public void save(int i) {
        try {
            methodInterceptor.intercept(this, save1, new Object[]{i}, null);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public void save(long i) {
        try {
            methodInterceptor.intercept(this, save2, new Object[]{i}, null);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }
}

测试类

public static void main(String[] args) {
    Target target = new Target();
    Proxy proxy = new Proxy();
    proxy.setMethodInterceptor(new MethodInterceptor() {
        @Override
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            System.out.println("before");
            return method.invoke(target, args);
        }
    });
    proxy.save();
    proxy.save(1);
    proxy.save(2L);
}

输出结果:

before
save()
before
save(int i)
before
save(long i)

Process finished with exit code 0

我们发现这里面interceptor方法的最后一个参数MthodProxy我们填的是null,这个参数主要的作用就是避免反射调用,而是直接调用方法。

接下来我们就来讲讲这个方法的原理:

MthodProxy原理
首先我们来看一下`MethodProxy`怎么使用。

它有两个方法分别为:

invoke配合目标使用,直接调用方法无需反射。

invokeSuper配合代理使用,也不许反射直接调用方法。

proxy.setMethodInterceptor(new MethodInterceptor() {
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("before");
        methodProxy.invoke(target, args);
        methodProxy.invokeSuper(proxy, args);
        return method.invoke(target, args);
    }
});

其实底层原理也是通过创建两个代理对象,一个处理invoke方法,一个处理invokeSuper方法,接下来我们称这两个代理对象为FastClass,因为这两个代理类都是FastClass的子类。

我们可以查看源码:

这是一个抽象类,我们查看一下它的抽象方法:

其中最重要的两个方法就是这个invokegetIndex

接下来我们先看一下这个MethodProxy怎么使用。

首先我们需要在代理类中创建三个方法用来直接调用父类的方法:

public void saveSuper(){
    super.save();
}

public void saveSuper(int i){
super.save(i);
}

public void saveSuper(long i){
    super.save(i);
}

然后我们创建MethodProxy

static Method save0;
static Method save1;
static Method save2;
static MethodProxy save0Super;
static MethodProxy save1Super;
static MethodProxy save2Super;
static{
    try {
        save0 = Target.class.getMethod("save");
        save1 = Target.class.getMethod("save", int.class);
        save2 = Target.class.getMethod("save", long.class);
        save0Super = MethodProxy.create(Target.class, Proxy.class, "()V", "save", "saveSuper");
        save1Super = MethodProxy.create(Target.class, Proxy.class, "(I)V", "save", "saveSuper");
        save2Super = MethodProxy.create(Target.class, Proxy.class, "(J)V", "save", "saveSuper");
    } catch (NoSuchMethodException e) {
        throw new NoSuchMethodError(e.getMessage());
    }
}

我们通过create方法创建,里面的参数

参数一target:目标类的class对象。

参数二proxy:代理类的class对象

参数三desc:方法表述,其中前面的括号用来表示参数,什么都没有就是无参,I 代表intJ代表long

参数四name:表示代理方法名称。

参数五name:调用原方法的名称。

这样我们把创建好的MethodProxy传入到参数中,进行调用即可。

接下来我们来模拟一下其中的原理。

之前说了MethodProxy内部会创建两个代理类,一个用于invoke调用目标类的使用,一个用于invokeSuper调用代理类的使用。

接下来我们就一起模拟一下:

首先创建一个类TargetClass用来模拟调用目标类:

这里我们就不继承FastClass了,因为那样的话需要重写很多方法,我们就直接模拟实现。

我们需要上面说的那两个重要的方法。

public class TargetClass {

    /**
     * 这个方法是通过签名来获取到目标方法的编号
     * 这里我们就假设一下:
     *      Target
     *        save()        0
     *        save(int)     1
     *        save(long)    2
     *  其中签名包括:方法名称、参数、返回值
     */
    public int getIndex(Signature signature){

    }

    // 通过 getIndex 获得的编号,直接
    public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException{

    }

}

其中getIndex这个方法的签名就是在我们调用MethodProxycreate方法时创建了FastClass代理类,然后通过create的方法参数(其中desc和代理的名称就组成了签名),就能够确定每个MethodProxy代表的是那个方法签名并且对应了哪个方法,然后会把所有的MethodProxy都加载到一个代理类中。

这里我们就模拟通过参数直接创建好签名和对应的编号:

static Signature s0 = new Signature("save", "()V");
static Signature s1 = new Signature("save", "(I)V)");
static Signature s2 = new Signature("save", "(J)V");

    /**
     * 这个方法是通过签名来获取到目标方法的编号
     * 这里我们就假设一下:
     *      Target
     *        save()        0
     *        save(int)     1
     *        save(long)    2
     *  其中签名包括:方法名称、参数、返回值
     */
    public int getIndex(Signature signature){
        if (s0.equals(signature)){
            return 0;
        }else if(s1.equals(signature)){
            return 1;
        }else if(s2.equals(signature)){
            return 2;
        }
        return -1;
    }

这里给好编号之后,到后门我们调用MethodProxy因为每个方法传递的是不同的,所以当调用的时候,它就知道自己是哪个编号了,然后通过编号就可以调用相关的目标方法了。

// 通过 getIndex 获得的编号,直接调用目标方法。
public Object invoke(int index, Object target, Object[] args) {
    if (index == 0){
        ((Target) target).save();
        return null;
    }else if(index == 1){
        ((Target) target).save((int)args[0]);
        return null;
    }else if(index == 2){
        ((Target) target).save((long)args[0]);
        return null;
    }else{
        throw new RuntimeException("无此方法");
    }
}

这里我们就假设模拟一下save()方法,当save()创建MethodProxy时,就会通过签名获取到编号0,然后调用的时候知道自己的编号,就可以在invoke方法中找到正确的方法进行调用。

那么按照上面的TargetClass,我们就可以快速的写出ProxyClass,只不过注意的是,这里面调用的是代理类的saveSuper方法,否则就会循环嗲用。

public class ProxyClass {
    static Signature s0 = new Signature("saveSuper", "()V");
    static Signature s1 = new Signature("saveSuper", "(I)V)");
    static Signature s2 = new Signature("saveSuper", "(J)V");


    public int getIndex(Signature signature){
        if (s0.equals(signature)){
            return 0;
        }else if(s1.equals(signature)){
            return 1;
        }else if(s2.equals(signature)){
            return 2;
        }
        return -1;
    }

    // 通过 getIndex 获得的编号,直接调用目标方法。
    public Object invoke(int index, Object proxy, Object[] args) {
        if (index == 0){
            ((Proxy) proxy).saveSuper();
            return null;
        }else if(index == 1){
            ((Proxy) proxy).saveSuper((int)args[0]);
            return null;
        }else if(index == 2){
            ((Proxy) proxy).saveSuper((long)args[0]);
            return null;
        }else{
            throw new RuntimeException("无此方法");
        }
    }
}

Spring 选择代理

上述我们提到了,`aop`底层有 `jdk`和`cglib`两种代理方式,但是 Spring 是怎么选择何时用什么代理的呢?

两种切面

在了解这个之前我们需要先了解清楚 Spring 中的两种不同切面的概念:
/*	aspect =
        通知1(advice) + 切点1(pointcut)
        通知2(advice) + 切点2(pointcut)
        通知3(advice) + 切点3(pointcut) 
*/
@Aspect  // 切面
class MyAspect{

    @Before("execution(* foo())")   
    public void before(){
        System.out.println("前置增强");
    }

    @After("execution(* foo())")
    public void after(){
        System.out.println("后置增强");
    }
}

aspect 切面:里面可以包含多个通知和切点,通常使用@Aspect注解表明这是一个切面,然后在里面使用
@Before注解加上方法体表示这是一个前置通知,然后在@Before注解里面写上切点表达式表示这是一个切点。

/* 
    advisor = 更细粒度的切面,里面包含一个通知和一个切点
*/

通常我们都是使用aspect切点的,但是一般aspect切点在底层都是会划分为多个advisor切点,所以说advisor切点的粒度更细。

选择代理规则:

在这里我们会进行自己手动模型 Spring 的选择代理规则。
模型底层切面增强
再此之前我们需要先准备一个接口和类
interface I1{
    void foo();

    void bar();
}

static class Target1 implements I1{
    public void foo() {
        System.out.println("target1 foo");
    }


    public void bar() {
        System.out.println("target1 bar");
    }
}

static class Target2{

    public void foo() {
        System.out.println("Target2 foo");
    }

    public void bar() {
        System.out.println("Target2 bar");
    }
}

然后我们需要准备:

  1. 一个切点

切点底层是一个 PointCut的接口,所以我们可以先查看一下源码(注意这里有很多个PointCut我们需要查看的是springframework.aop包下的)

进来我们可以发现切点就是用来匹配的,通过方法名我们就可以发现。

getClassFilter进行类过滤的

getMethodMatcher通过方法进行匹配的(这个方法最重要)

这个接口还提供了很多实现类(在接口上按住ctrl+alt+B可以查看接口的实现类)

这里我们使用一个AspectJExpressionPointcut通过切点表达式匹配的一个实现类。

// 1. 一个切点
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
// 设置切点表达式
pointcut.setExpression("execution(* foo())");
  1. 一个通知

Spring 中通知也有很多接口,这里介绍一个最底层的通知,其它的实现最终都是转换为这个通知。这个通知就是MethodInterceptor(环绕通知)。

进来查看源码

这个接口就只有一个方法,这个方法就是用来进行增强的,我们可以通过编写来进行增强我们的方法,其中最重要的一点就是,这个方法其实是一个环绕通知

// 2. 一个通知
MethodInterceptor advice = new MethodInterceptor() {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("before");
        Object result = invocation.proceed();  // 调用目标方法
        System.out.println("after");
        return result;
    }
};

// 改写为 lambda 表达式
MethodInterceptor advice = invocation -> {
    System.out.println("before");
    Object result = invocation.proceed();  // 调用目标方法
    System.out.println("after");
    return result;
};
  1. 一个切面

这里切面使用的就是上述说的只有一个切点和一个通知的 Advisor(因为后面所有的切面都是转化成一个个的 Advisor切面)。

这个切面也是一个接口:

这里使用一个比较简单的实现:

这里在构造方法上就可以传递一个切点和一个通知

// 3. 一个切面
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
  1. 创建代理

因为 aop 的增强都是通过代理对象来增强的,所以这里我们也需要生成代理。不过这里我们不需要使用 jdk 或者 cglib 的 api 去创建代理,可以直接使用一个工厂类:ProxyFactory。(内部会根据不同的情况去选择 jdk 的实现或者 cglib 的实现)。

// 4. 创建代理
Target1 target1 = new Target1();
ProxyFactory factory = new ProxyFactory();
factory.setTarget(target1);  // 设置目标
factory.addAdvisor(advisor); // 设置切面
I1 proxy = (I1) factory.getProxy();  // 获得代理

最后调用代理对象的方法,查看是否增强

proxy.foo();
proxy.bar();

before

target1 foo

after

target1 bar

可以看到 foo 方法是被增强了的。

同时我们还可以查看一下这时ProxyFactory创建出来的代理是通过什么方式创建出来的,我们可以通过查看类名查看

Target1 target1 = new Target1();
ProxyFactory factory = new ProxyFactory();
factory.setTarget(target1);  // 设置目标
factory.addAdvisor(advisor); // 设置切面
I1 proxy = (I1) factory.getProxy();  // 获得代理
System.out.println(proxy.getClass());

class com.sahuid.a15.A15 T a r g e t 1 Target1 Target1 E n h a n c e r B y S p r i n g C G L I B EnhancerBySpringCGLIB EnhancerBySpringCGLIB$7864e0bf

可以查看这是通过 CGLIB 创建出来的代理。

但是到底什么时候选择 jdk 什么时候选择 cglib 呢?

下面我们一起学习一下:

代理选择
在创建代理的时候,`ProxyFactory`会读取一个属性变量`proxyTargetClass`这个属性变量是存在它的父类`ProxyConfig`当中。

代理的选择就与这个值有关。

代理选择的规则:

  1. **proxyTargetClass = false**,如果目标实现了接口,就使用 jdk 代理。
  2. **proxyTargetClass = false**,如果目标没有实现接口,就使用 cglib 代理。
  3. **proxyTargetClass = true**,使用 cglib 代理。(不管有没有实现接口)

这时就有一个疑问了:我们上面那个模型也实现了接口,但是为什么还是使用的 cglib 代理呢?

其实这是因为还少设置了一个值,告诉proxyfactory实现了什么接口。我们上面就只设置了目标是谁,切面是谁,但是如果不设置这个值,就没有办法告诉 proxyfactory是否这个目标实现了接口,会默认没有实现接口。

我们设置一下:

 factory.setInterfaces(target1.getClass().getInterfaces()); // 设置实现的接口

再来观察一下代理对象:

class com.sahuid.a15.$Proxy0

就会发现变成了 jdk 实现

我们再测试一下第三种情况,将proxyTargetClass设置为 true,即使实现了接口也会调用 cglib 的实现。

    factory.setInterfaces(target1.getClass().getInterfaces()); // 设置实现的接口
    factory.setProxyTargetClass(true);

class com.sahuid.a15.A15 T a r g e t 1 Target1 Target1 E n h a n c e r B y S p r i n g C G L I B EnhancerBySpringCGLIB EnhancerBySpringCGLIB$c84c94a9

再模拟一下第二种情况,我们将proxyTargetClass设置为 false,然后使用 Target2 没有实现接口。

Target2 target2 = new Target2();
ProxyFactory factory = new ProxyFactory();
factory.setTarget(target2);  // 设置目标
factory.addAdvisor(advisor); // 设置切面
factory.setInterfaces(target2.getClass().getInterfaces()); // 设置实现的接口
factory.setProxyTargetClass(false);
Target2 proxy = (Target2) factory.getProxy();  // 获得代理
System.out.println(proxy.getClass());
proxy.foo();
proxy.bar();

class com.sahuid.a15.A15 T a r g e t 2 Target2 Target2 E n h a n c e r B y S p r i n g C G L I B EnhancerBySpringCGLIB EnhancerBySpringCGLIB$659f8203

切点匹配

之前我们已经模拟了底层切面的增强,然后使用了 `AspectJExpressionPointcut`作为切点,通过切点表达式去匹配方法增强,这里我们来学习和研究更多种切点匹配的模式。

再次之前准备模拟数据

static class T1{
    @Transactional
    public void foo(){

    }

    public void bar(){
        System.out.println("T1.. bar");
    }
}

匹配特定的某个方法

这里我们只匹配 `bar()`方法,而不匹配`foo()`方法。
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* bar())");

MethodInterceptor advice = invocation -> {
    System.out.println("before....");
    Object result = invocation.proceed();
    System.out.println("after....");
    return result;
};

DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);

T1 t1 = new T1();
ProxyFactory factory = new ProxyFactory();
factory.setTarget(t1);
factory.addAdvisor(advisor);
factory.setInterfaces(t1.getClass().getInterfaces());
T1 proxy = (T1) factory.getProxy();
proxy.bar();

before…

T1… bar

after…

但是具体是怎么判断是否哪个方法应该增强的呢?

这里具体依赖于AspectJExpressionPointcut的一个方法matches。这个方法会返回一个 boolean 值,如果为 true 就说明这个方法与切点匹配,如果返回 false 就说明与方法不匹配。

System.out.println(pointcut.matches(T1.class.getMethod("foo"), T1.class));
System.out.println(pointcut.matches(T1.class.getMethod("bar"), T1.class));

false

true

后续代理就会进行这个检查,如果匹配成功的话再进行增强。

匹配某个注解的方法

这里需要换一种写法 `@annotation`来匹配,需要使用包名加注解名称
AspectJExpressionPointcut pt2 = new AspectJExpressionPointcut();
pt2.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)");
System.out.println(pt2.matches(T1.class.getMethod("foo"), T1.class));
System.out.println(pt2.matches(T1.class.getMethod("bar"), T1.class));

true

false

这里只是模拟了一个注解的匹配方式,而Transactional注解在 Spring 底层中真正的匹配方式并不是这样的。

上述的两种方式只能匹配方法(切点表达式),而不能匹配类。

Transcational 注解的匹配方式(任意匹配方式)

而`Transactional`注解可以使用在:
  • 方法上生效
  • 类上生效
  • 同时还可以使用在接口上,使其实现类生效。

所以使用不一样的匹配方式。

接下来我们来模拟一下Transactional注解的生效方式。

准备类和接口:

    static class T1{
        @Transactional
        public void foo(){
        }
        public void bar(){
        }
    }

    @Transactional
    static class T2{
        public void foo(){
            
        }
    }

    @Transactional
    interface I3{
        void foo();
    }

    static class T3 implements I3{
        public void foo() {
            
        }
    }

这里使用一个StaticMethodMatcherPointcut抽象类,重写它的方法来进行匹配,两个参数分别是方法和目标类。可以分别查询方法和类上是否存在某个接口。

查看某个方法/类是否有某个注解可以使用反射。

不过这里使用 Spring 封装好的一个类MergedAnnotations,可以传入方法或者的参数。调isPresent方法查看是否存在某个注解。

StaticMethodMatcherPointcut pt3 = new StaticMethodMatcherPointcut() {
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        // 查看某个方法是否存在 Transactional 注解
        MergedAnnotations annotations = MergedAnnotations.from(method);
        if (annotations.isPresent(Transactional.class)) {
            return true;
        }
        // 查看某个类上是否存在 Transactional 注解
        annotations = MergedAnnotations.from(targetClass);
        if (annotations.isPresent(Transactional.class)) {
            return true;
        }
        return false;
    }
};

进行测试:

System.out.println(pt3.matches(T1.class.getMethod("foo"), T1.class));
System.out.println(pt3.matches(T1.class.getMethod("bar"), T1.class));
System.out.println(pt3.matches(T2.class.getMethod("foo"), T2.class));

true

false

true

不过测试接口的时候我们发现会失败

System.out.println(pt3.matches(T3.class.getMethod("foo"), T3.class));

false

查看 from方法的源码我们可以发现:

这里有个搜索策略是SearchStrategy.DIRECT意思是只会查询一层,只会查看本类中有没有添加注解,不会查看其实现的接口。(所以 T3 不会匹配成功)

想要匹配成功我们只需要调用from方法时,改变一下查询策略即可。

这个枚举表示从继承树上进行查找:

annotations = MergedAnnotations.from(targetClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);
if (annotations.isPresent(Transactional.class)) {
    return true;
}

true

@Aspect 和 Advisor

这一节主要来讲 @Aspect 高级切面类和 Advisor 低级切面类的关系,并且会模拟怎么最终把高级切面类都转化为低级的切面类。

先创建模拟用的类:

static class Target1{
    public void foo(){
        System.out.println("target1 foo");
    }
}

static class Target2{
    public void bar(){
        System.out.println("target2 bar");
    }
}

然后创建两个切面类

@Aspect
static class Aspect1{
    @Before("execution(* foo())")
    public void before(){
        System.out.println("aspect1 before");
    }

    @After("execution(* foo())")
    public void after(){
        System.out.println("aspect1 after");
    }
}

@Configuration
static class Config{
    @Bean
    public Advisor advisor3(Advice advice3){
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* foo())");
        return new DefaultPointcutAdvisor(pointcut, advice3);
    }

    @Bean
    public MethodInterceptor advice3(){
        return new MethodInterceptor() {
            @Override
            public Object invoke(MethodInvocation invocation) throws Throwable {
                System.out.println("before");
                Object result = invocation.proceed();
                System.out.println("after");
                return result;
            }
        };
    }
}

这里使用 Aspect 注解创建了高级切面,然后使用容器配置类的方式创建了低级切面。

最后将切面交给 Spring 容器进行管理

GenericApplicationContext context = new GenericApplicationContext();
// 注册 bean
context.registerBean("aspect1", Aspect1.class);
context.registerBean("config", Config.class);
// 注册 bean 工厂后处理器,去解析 @Bean 注解
context.registerBean(ConfigurationClassPostProcessor.class);

// 准备好容器
context.refresh();

// 打印 bean 的名称
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
    System.out.println(beanDefinitionName);
}

aspect1

config

org.springframework.context.annotation.ConfigurationClassPostProcessor

advisor3

advice3

AnnotationAwareAspectJAutoProxyCreator

接下来进入重点,介绍一个类。

它的一个 bean 的后置处理器,作用是:

  1. 解析所有的切面,包括高级切面和低级切面,如果是高级切面会转化为低级切面。
  2. 拿到所有的切面,然后为其创建代理。

这个类就是AnnotationAwareAspectJAutoProxyCreator,我们需要把它也注入到容器当中。

// 注册 bean 后处理器, AnnotationAwareAspectJAutoProxyCreator
context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);

这个类实现了BeanPostProcessor接口,所以是一个 bean 的后处理器,它会被使用在 bean 的整个生命周期中。

这个类一般都是在初始化后去调用,但是也有可能会在依赖注入前调用(为了是解决依赖循环创建代理,后面再讲)。

如果我们就直接把这个类注入到容器当中,那么 Spring 会帮我们自动完成整个创建流程,我们就不是很好能够观察到这个类的作用,接下来我们手动调用这个类的方法来模拟。

接下来我们去源码中查看一下最为重要的两个方法:(ctrl + F12 查看内部方法)

我们会发现这两个方法都是protected修饰的,所以我们想要调用就需要使用反射调用或者创建一个子类去调用,或者是在这个源码相同的包下创建类去使用。

findEligibleAdvisor
这个方法就是去查找**有资格的**切面(Advisor),如果是低级切面就直接保存到集合当中,如果是**高级切面就会将高级切面转化为低级切面**然后再保存到集合当中。

这个我们通过反射去获取这个方法:

// 反射获取方法
AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
Method findEligibleAdvisors = AbstractAdvisorAutoProxyCreator.class.getDeclaredMethod("findEligibleAdvisors", Class.class, String.class);
findEligibleAdvisors.setAccessible(true);

注意:

这个方式其实是它父类中的方法,所以我们通过反射的时候需要去父类中查找方法。

方法说明:

这个方法一共有两个参数。

第一个是目标对象 class,这个对象主要是用来,当对象传进来之后,方法会去从切面中查找能够匹配上目标方法的切面,如果是低级切面直接加入集合,如果是高级切面先转化为低级切面再加入集合。

第二个参数是,如果被容器管理需要传入 bean 的名字,但是这里并没有被容器管理,所以就随便填一个。

进行调用:

List<Advisor> advisors = (List<Advisor>) findEligibleAdvisors.invoke(creator, Target1.class, "target1");
for (Advisor advisor : advisors) {
    System.out.println(advisor);
}

org.springframework.aop.interceptor.ExposeInvocationInterceptor.ADVISOR

org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [com.sahuid.a17.A17$Config$1@587e5365]

InstantiationModelAwarePointcutAdvisor: expression [execution(* foo())]; advice method [public void com.sahuid.a17.A17$Aspect1.before()]; perClauseKind=SINGLETON

InstantiationModelAwarePointcutAdvisor: expression [execution(* foo())]; advice method [public void com.sahuid.a17.A17$Aspect1.after()]; perClauseKind=SINGLETON

我们可以查看到一共有四个切面。

这和我们想象的并不一样呀,我们一共一个高级切面一个低级切面,怎么会有四个切面呢

其中第一个切面是一个环绕通知,它是Spring自动加的具体作用后续会说明。

然后第二个切面我们可以看得出DefaultPointcutAdvisor说明这是我们设置的低级切面。

然后第三第四个切面我们可以看到beforeafter说明这是我们高级切面里面的两个通知。

这里我们就可以知道了,findEligibleAdvisor这个方法会把高级切面里面的每个切点 + 通知都会转化为一个低级切面。

所以一共有四个。

wrapIfNecessary
这个方法就是先去检查是否满足要求,如果满足要求再去创建代理,否则不去创建代理。

如何查看是否满足要求然后创建切面呢?

其实wrapIfNecessary方法内部调用了findEligibleAdvisor方法,只有方法返回的集合不为空就说明有匹配的切面,就为其创建代理

接下来我们也模拟调用一下:

先反射获取方法:

AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
Method wrapIfNecessary = AbstractAutoProxyCreator.class.getDeclaredMethod("wrapIfNecessary", Object.class, String.class, Object.class);
wrapIfNecessary.setAccessible(true);

注意:

这个方法是在 AbstractAutoProxyCreator这个类下

参数说明:

第一个参数就是目标类,当前我们是在环境外部自行模拟调用的,所以需要自己 new 对象,当在容器中后,这个目标类就是通过 Spring 进行调用的。

第二个参数就是容器的名称对当前模拟没有什么作用,第三个参数也是所以就随便填即可。

接下来调用方法:

Object o1 = wrapIfNecessary.invoke(creator, new Target1(), "target1", "target1");
System.out.println(o1.getClass());
Object o2 = wrapIfNecessary.invoke(creator, new Target2(), "target2", "target2");
System.out.println(o2.getClass());

我们会发现方法会返回一个 Object 的对象。然后我们通过输出一下可以查看是否是代理。

不过我们在不输出之前也可以知道,因为 Target1 类中的 foo方法切点匹配,所以内部调用findEligibleAdvisor方法返回的集合不为空,所以会被增强,而 Target2 不会被增强。

查看一下结果

class com.sahuid.a17.A17 T a r g e t 1 Target1 Target1 E n h a n c e r B y S p r i n g C G L I B EnhancerBySpringCGLIB EnhancerBySpringCGLIB$47ce25fe

class com.sahuid.a17.A17$Target2

创建代理的时机

上面我们讲过`AnnotationAwareAspectJAutoProxyCreator`这个类是一个 bean 后处理器,一般会在**依赖注入前**或者**初始化后**来调用,调用的目的就是因为这个类可以创建代理对象。

接下来我们就一起来看看到底什么时候创建代理,又为什么在这个时候创建代理?

提前准备好类

public static void main(String[] args) {
    GenericApplicationContext context = new GenericApplicationContext();
    context.registerBean(ConfigurationClassPostProcessor.class);
    context.registerBean(Config.class);
    context.refresh();
    context.close();
}



@Configuration
static class Config{
    @Bean // 解析 @Aspect 注解  创建代理
    public AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator(){
        return new AnnotationAwareAspectJAutoProxyCreator();
    }

    @Bean // 解析 @Autowired 注解
    public AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor(){
        return new AutowiredAnnotationBeanPostProcessor();
    }

    @Bean // 解析 @PostConstruct 注解
    public CommonAnnotationBeanPostProcessor commonAnnotationBeanPostProcessor(){
        return new CommonAnnotationBeanPostProcessor();
    }

    @Bean
    public Advisor advisor(MethodInterceptor advice){
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* foo())");
        return new DefaultPointcutAdvisor(pointcut, advice);
    }

    @Bean
    public MethodInterceptor advice(){
        return invocation -> {
            System.out.println("before");
            Object result = invocation.proceed();
            return result;
        };
    }

    @Bean
    public Bean1 bean1(){
        return new Bean1();
    }

    @Bean
    public Bean2 bean2(){
        return new Bean2();
    }
}

static class Bean1{
    public void foo(){

    }

    public Bean1(){
        System.out.println("Bean1()");
    }

    @PostConstruct
    public void init(){
        System.out.println("Bean1 init()");
    }
}

static class Bean2{
    public Bean2(){
        System.out.println("Bean2");
    }

    @Autowired
    public void setBean1(Bean1 bean1){
        System.out.println("Bean2 setBean1(bean1) class is" + bean1.getClass());
    }

    @PostConstruct
    public void init(){
        System.out.println("Bean2 init()");
    }
}
初始化后创建代理
这时候我们发现这个`Bean1`和`Bean2`的依赖关系是单向的,`Bean2`依赖于`Bean1`

然后运行程序我们可以发现输出结果为:

Bean1()
Bean1 init()
Creating implicit proxy for bean 'bean1' with 0 common interceptors and 2 specific interceptors 
Bean2
Bean2 setBean1(bean1) class isclass com.sahuid.a17.A17_1$Bean1$$EnhancerBySpringCGLIB$$f175f1d5
Bean2 init()

从这里我们可以发现

首先是实例化创建了 bean1 ,然后初始化了 bean 1。

通过这段可以发现是初始化之后创建了代理。

Creating implicit proxy for bean 'bean1' with 0 common interceptors and 2 specific interceptors 

这里就是在初始化之后创建了代理!!

依赖注入前创建代理
这时我们向 bean 1 也依赖注入 bean 2,然后在打印看一下:
static class Bean1{
    public void foo(){

    }

    @Autowired
    public void setBean2(Bean2 bean2){
        System.out.println("Bean1 setBean2(bean2) class is" + bean2.getClass());
    }

    public Bean1(){
        System.out.println("Bean1()");
    }

    @PostConstruct
    public void init(){
        System.out.println("Bean1 init()");
    }
}
Bean1()
Bean2()
[TRACE] 16:48:00.458 [main] o.s.a.a.a.AnnotationAwareAspectJAutoProxyCreator - Creating implicit proxy for bean 'bean1' with 0 common interceptors and 2 specific interceptors 
Bean2 setBean1(bean1) class isclass com.sahuid.a17.A17_1$Bean1$$EnhancerBySpringCGLIB$$b5517fb7
Bean2 init()
Bean1 setBean2(bean2) class isclass com.sahuid.a17.A17_1$Bean2
Bean1 init()

这时我们可以发现是在依赖注入前的时候就创建了代理。

这是为什么呢?

因为这里面存在了循环依赖:了解bean的生命周期就知道首先需要实例化然后依赖注入然后初始化。所以这里出现的情况就是:

一开始实例化然后准备依赖注入,但是发现需要 bean2 ,但是 bean 2 并没有注入到容器中,所以开始注入 bean 2 ,然后实例化 bean 2,当要依赖注入的时候发现需要 bean 1 但是 bean 1还在创建当中。这时如果还是继续走 bean 1的创建流程就会又从 bean 1bean 2 ,从 bean 2bean 1陷入循环,这种情况就成为依赖循环

为了解决这个依赖循环的问题,所以就可以在 bean 2 需要依赖注入之前先创建一个 bean 1的代理对象,然后将其注入这时 bean 2就完成了创建。然后将其注入到bean 1bean 1也可以完成创建。

整个流程就是

bean1 实例化 -> bean2 实例化 -> 创建 bean1 的代理对象 -> bean 2 依赖注入 ->
bean2 初始化 -> bean1 依赖注入 -> bean 1 初始化。

所以这个代理就是在依赖注入之前进行的,作用就是防止依赖循环

高级切面转换低级切面

上面我们也讲过,所有的`@Aspect`注解的高级切面最后都会转化为`Advisor`这低级切面,但是具体怎么转化的呢,而且到底转化成什么样的`Advisor`低级切面呢,接下来了解一下:

提起准备相关的类:

static class Aspect{
    @Before("execution(* foo())")
    public void before1(){
        System.out.println("before1 foo");
    }

    @Before("exe")
    public void before2(){
        System.out.println("before2 foo");
    }

    public void after(){
        System.out.println("after");
    }

    public void afterReturning(){
        System.out.println("afterReturning");
    }

    public void afterThrowing(){
        System.out.println("afterThrowing");
    }

    public void around(){
        System.out.println("around");
    }
}

接下来就模拟一下 @Before注解转化为低级切面。其中@Before标记的通知最后会转化为原始的AspectJMethodBeforeAdvice低级切面。

具体操作:

public static void main(String[] args) {
    // 创建切面对象实例工厂, 用户后续反射调用切面的方法
    AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());
    Method[] methods = Aspect.class.getDeclaredMethods();
    List<Advisor> list = new ArrayList<>();
    for (Method method : methods) {
        if (method.isAnnotationPresent(Before.class)){
            // 解析切点
            Before before = method.getAnnotation(Before.class);
            String expression = before.value();
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression(expression);
            // 创建通知类 Before 对应的通知类 ->> AspectJMethodBeforeAdvice
            AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);
            // 创建切面
            Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
            list.add(advisor);
        }
    }
    for (Advisor advisor : list) {
        System.out.println(advisor);
    }

输出:

org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice: advice method [public void com.sahuid.a17.A17_2$Aspect.before2()]; aspect name '']
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice: advice method [public void com.sahuid.a17.A17_2$Aspect.before1()]; aspect name '']

这样一个被@Before注解标注的高级切面就被转化为一个对应的低级初始切面AspectJMethodBeforeAdvice

最后介绍一下高级切面和归纳一下所有高级切面对应的切面:

  • @Before -> 前置通知
  • @AfterReturning -> 最终通知
  • @AfterThrowing -> 异常通知
  • @After -> 后置通知
  • @Around -> 环绕通知
注解对应的原始通知类
@BeforeAspectJMethodBeforeAdvice
@AfterReturningAspectJAfterReturningAdvice
@AfterThrowingAspectJAfterThrowingAdvice
@AfterAspectJAfterAdvice
@AroundAspectJAroundAdvice

静态通知调用

统一转化为环绕通知

上面我们介绍过了各个注解最后都会转化成对应的原始通知切面,但是这其实并不是程序最后调用的。程序最后会将所有的通知都转化成一个**环绕通知**`MethodInterceptor` 。但是具体为什么要这么转化呢,我们一起来了解一下:

其实无论ProxyFactory是基于哪种代理方式,最后干活的(调用 advice )的都是一个MethodInvacation的对象。

这个MethodInvacation对象不仅需要知道有哪些advice,还要知道目标有什么。然后最终的调用次序为:

调用需要保证一个有序性,先从外往里一层层调用,调用到目标之后再从里往外一层层掉。如果不是环绕通知的话就会导致调用的顺序无法保证。

从图可知,只有环绕通知才适合做advice,其他的 before、afterReturning等需要转化为环绕通知。

而如果本身就是环绕通知的就不需要转化为环绕通知了,例如:

  • AspectJAroundAdvice
  • AspectJAfterThrowingAdvice
  • AspectJAfterAdvice

(想看是否是环绕通知,只需要看是否实现了 MethodInterceptor接口)

接下来模拟一下:

首先我们还是先转化成低级切面

准备模拟类

static class Aspect{
    @Before("execution(* foo())")
    public void before1(){
        System.out.println("before1 foo");
    }

    @Before("execution(* foo())")
    public void before2(){
        System.out.println("before2 foo");
    }

    public void after(){
        System.out.println("after");
    }

    @AfterReturning("execution(* foo())")
    public void afterReturning(){
        System.out.println("afterReturning");
    }

    public void afterThrowing(){
        System.out.println("afterThrowing");
    }

        @Around("execution(* foo())")
        public Object around(ProceedingJoinPoint pjp) throws Throwable {
            try {
                System.out.println("around...before");
                return pjp.proceed();
            } finally {
                System.out.println("around...after");
            }
        }
}

模拟将@Before@AfterReturning@Around转化成低级切面

// 创建切面对象实例工厂, 用户后续反射调用切面的方法
AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());
Method[] methods = Aspect.class.getDeclaredMethods();
List<Advisor> list = new ArrayList<>();
for (Method method : methods) {
    if (method.isAnnotationPresent(Before.class)) {
        // 解析切点
        Before before = method.getAnnotation(Before.class);
        String expression = before.value();
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(expression);
        // 创建通知类 Before 对应的通知类 ->> AspectJMethodBeforeAdvice
        AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);
        // 创建切面
        Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
        list.add(advisor);
    } else if (method.isAnnotationPresent(AfterReturning.class)) {
        // 解析切点
        AfterReturning before = method.getAnnotation(AfterReturning.class);
        String expression = before.value();
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(expression);
        // 创建通知类 AfterReturning 对应的通知类 ->> AspectJAfterReturningAdvice
        AspectJAfterReturningAdvice advice = new AspectJAfterReturningAdvice(method, pointcut, factory);
        // 创建切面
        Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
        list.add(advisor);
    } else if (method.isAnnotationPresent(Around.class)) {
        // 解析切点
        Around before = method.getAnnotation(Around.class);
        String expression = before.value();
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(expression);
        // 创建通知类 Around 对应的通知类 ->> AspectJAfterReturningAdvice
        AspectJAroundAdvice advice = new AspectJAroundAdvice(method, pointcut, factory);
        // 创建切面
        Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
        list.add(advisor);
    }
}
for (Advisor advisor : list) {
    System.out.println(advisor);
}
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice: advice method [public void com.sahuid.a18.A18$Aspect.before1()]; aspect name '']
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJAfterReturningAdvice: advice method [public void com.sahuid.a18.A18$Aspect.afterReturning()]; aspect name '']
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJAroundAdvice: advice method [public void com.sahuid.a18.A18$Aspect.around()]; aspect name '']
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice: advice method [public void com.sahuid.a18.A18$Aspect.before2()]; aspect name '']

转化成环绕通知:

这里需要使用到代理工厂ProxyFactory,因为通常是创建完代理之后,然后调用代理方法的时候会转化成环绕通知,这里我们之间调用方法模拟一下。这个方法是
getInterceptorsAndDynamicInterceptionAdvice

参数1:被增强的方法

参数2:目标类

转化为环绕通知

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new Target1());
proxyFactory.addAdvisors(list);
List<Object> methodInterceptorList = proxyFactory.getInterceptorsAndDynamicInterceptionAdvice(Target1.class.getMethod("foo"), Target1.class);
for (Object o : methodInterceptorList) {
    System.out.println(o);
}
org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor@26a7b76d
org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor@4abdb505
org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor@7ce6a65d
org.springframework.aop.aspectj.AspectJAroundAdvice: advice method [public void com.sahuid.a18.A18$Aspect.around()]; aspect name ''

从输出结果可以得知:

像不是环绕通知的 AspectJMethodBeforeAdvice这种会转化为MethodBeforeAdviceInterceptor。而像AspectJAroundAdvice就不换转换,还是本身。

调用链执行

上面我们也说过,这所有的通知最后都会转化为环绕通知,转化完的环绕通知就会通过一个调用链的方式依次调用,这个调用链就有点像拦截器或者过滤器。

接下我们在上面代码的基础上继续模拟调用:

这里执行调用的是一个接口MethodInvocation,我们实现一个它的实现类ReflectiveMethodInvocation

注意:

这里的构造方法是protected的所以可以通过创建反射或者继承父类的方式调用,这里我们就通过继承父类的方法调用,只需要在方法后面打一个大括号即可。

MethodInvocation invocation = new ReflectiveMethodInvocation(null, target, Target1.class.getMethod("foo"), new Object[0], Target1.class, methodInterceptorList){};
invocation.proceed();

第一个参数是一个代理,这个在调用期间基本上用不到就传一个null

第二个参数是一个目标类对象,这里就是new出来的 target

第三个参数是一个method,就通过反射拿到foo方法

第四个参数是一个数组,就是方法的参数,这里没有就创建一个空数组

第五个参数是目标类的一个class对象

第六个参数就是我们上面获取到的环绕通知的list

当这时我们进行调用时:

发现报错了,具体原因是因为MethodInvocation没有找到。

这时就有一个疑问了?

明明我们都创建了对象才调用的方法,为什么会没有找到呢。

原因是因为其实我们环绕通知的每一个切面都需要MethodInvocation然后调用
proceed方法。这就有点像过滤器,每次过滤最后都需要调用filterChain.doFilter方法一样。

而怎么给每个切面都添加一个MethodInvocation呢,这里需要一个环绕通知,是添加到最开始的环绕通知,这个环绕通知是ExposeInvocationInterceptor。这也解决了我们上面第一次高级切面转化为低级切面时,发现的明明自己定义了三个却有四个,这多出来的就是这个环绕通知切面。

这个环绕通知实现的原理就是向当前线程里面设置一个MethodInvocation,然后哪个切面需要就像当前线程取即可,底层是根据ThreadLocal实现的。

接下来我们把它加一下

我们需要在添加所有切面之前加入这个,添加的是一个单例对象。

proxyFactory.addAdvice(ExposeInvocationInterceptor.INSTANCE);
proxyFactory.addAdvisors(list);

最后再调用执行一下:

before1 foo
before2 foo
around...before
target1 foo
around...after
afterReturning

最后发现调用符合我们的调用链的执行方式

模拟实现调用链

先创建两个环绕通知,直接实现`MethodInterceptor`创建环绕通知。
static class Advice1 implements MethodInterceptor{

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("advice1 before");
        Object result = invocation.proceed();
        System.out.println("advice1 after");
        return result;
    }
}

static class Advice2 implements MethodInterceptor{

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("advice2 before");
        Object result = invocation.proceed();
        System.out.println("advice2 after");
        return result;
    }
}

再创建一个目标类:

static class Target{
    public void foo(){
        System.out.println("target foo");
    }
}

再创建一个调用链对象,实现MethodInvocation接口。

然后创建一个成员变量:

target:目标类对象,用来反射调用方法时使用

method:被增强的方法

args:方法参数

methodInterceptorList:环绕通知切面集合。

static class MyInvocation implements MethodInvocation{

    private Object target;
    private Method method;
    private Object[] args;
    private List<MethodInterceptor> methodInterceptorList;
    private int count = 1;

    public MyInvocation(Object target, Method method, Object[] args, List<MethodInterceptor> methodInterceptorList) {
        this.target = target;
        this.method = method;
        this.args = args;
        this.methodInterceptorList = methodInterceptorList;
    }

    @Override
    public Method getMethod() {
        return method;
    }

    @Override
    public Object[] getArguments() {
        return args;
    }

    @Override
    public Object proceed() throws Throwable {
        if (count > methodInterceptorList.size()){
            return method.invoke(target,args);
        }
        MethodInterceptor methodInterceptor = methodInterceptorList.get(count++ - 1);
        return methodInterceptor.invoke(this);
    }

    @Override
    public Object getThis() {
        return target;
    }

    @Override
    public AccessibleObject getStaticPart() {
        return method;
    }
}

其中的proceed方法最重要,其中运用了递归的原理。

首先我们需要确定什么时候调用目标方法,假设切面有两个,那么两个切面之后就会调用目标方法,所以我们设置一个变量用来计数当前执行的切面数。

然后如果没有到执行目标方法时,我们就先直接切面的方法,从list中取然后调用,调用完之后count++

但是单纯方法看,根本看不出来递归再哪里,所以我们需要结合着方法来看看。

// advice1
public Object invoke(MethodInvocation invocation) throws Throwable {
    System.out.println("advice1 before");
    Object result = invocation.proceed();
    System.out.println("advice1 after");
    return result;
}

// advice2
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
    System.out.println("advice2 before");
    Object result = invocation.proceed();
    System.out.println("advice2 after");
    return result;
}

// MethodInvocation
public Object proceed() throws Throwable {
if (count > methodInterceptorList.size()){
    return method.invoke(target,args);
}
MethodInterceptor methodInterceptor = methodInterceptorList.get(count++ - 1);
return methodInterceptor.invoke(this);
}

当外面调用的MethodInvocationproceed方法时:

  • 进入MethodInvocationproceed方法
    • 判断是否执行目标方法 – 不需要
    • 获取advice1的切面通知
    • 调用advice1invoke方法
      • 输出advice1 before
      • 调用通过ExposeInvocationInterceptor传入的MethodInvocationproceed方法
        • 判断是否执行目标方法 — 不需要
        • 获取advice2的切面通知
        • 调用advice2invoke方法
          • 输出advice2 before
          • 调用MethodInvocationproceed方法
            • 判断是否执行目标方法 – 需要
            • 执行targetfoo方法
            • 返回
          • 输出advice2 after
          • 返回
        • 返回
      • 输出advice1 after
      • 返回
    • 返回
  • 执行完成

最后整个输出结果为:

advice1 before
advice2 before
target foo
advice2 after
advice1 after
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值