AOP

AspectJ 编译器增强

定义 Service 类和代理类:

@Service
public class MyService {
    private static final Logger log = LoggerFactory.getLogger(MyService.class);
    public void foo() {
        log.info("foo()");
    }
}

@Aspect
public class MyAspect {
    private static final Logger log = LoggerFactory.getLogger(MyAspect.class);

    @Before("execution(* indi.mofan.service.MyService.foo())")
    public void before() {
        log.info("before()");
    }
}

注意代理类并未被 Spring 管理。

indi.mofan.A10Application                : service class: class indi.mofan.service.MyService
indi.mofan.aop.MyAspect                  : before()
indi.mofan.service.MyService             : foo()

这里并未使用代理,而是引用了 AspectJ 的插件:

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>aspectj-maven-plugin</artifactId>
            <version>1.11</version>
            <configuration>
                <complianceLevel>1.8</complianceLevel>
                <source>8</source>
                <target>8</target>
                <showWeaveInfo>true</showWeaveInfo>
                <verbose>true</verbose>
                <Xlint>ignore</Xlint>
                <encoding>UTF-8</encoding>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <!-- use this goal to weave all your main classes -->
                        <goal>compile</goal>
                        <!-- use this goal to weave all your test classes -->
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

注意在引入插件后,不能使用 idea 自带的 Java c 命令编译,要使用 maven 提供的 compile 命令编译,这样插件才会生效。
image.png
查看编译后的 .class 文件:

@Service
public class MyService {
    private static final Logger log = LoggerFactory.getLogger(MyService.class);

    public MyService() {
    }

    public void foo() {
        MyAspect.aspectOf().before();
        log.info("foo()");
    }
}

这进一步说明了我们当前的方式是增强不是代理,属于编译时增强。
代理本质上是通过重写方法实现的,那么我们在方法上添加 static 关键字:

public class MyService {
    private static final Logger log = LoggerFactory.getLogger(MyService.class);
    public static void foo() {
        log.info("foo()");
    }
}

仍然成功增强,再次证明使用的是增强。

Agent 类加载

上面的方法是通过一个额外的插件来完成的,接下来介绍的方法是通过在类加载阶段对代码进行动态增强实现的,换言之,编译后生成的文件与编译前文件保持一致。
在 JVM 参数中添加:

-javaagent:D:\environment\Maven\3.6.3-repository\.m2\repository\org\aspectj\aspectjweaver\1.9.7\aspectjweaver-1.9.7.jar

从输出的内容可以看到 service.getClass() 打印出的信息也是原始类的 Class 信息,而非代理类的 Class 信息。因此不依赖 Spring 容器,直接 new 一个 MyService 实例并调用其 foo() 方法也能达到增强的目的。
接下来,我们将使用 Arthas 来反编译运行时的.class 文件。

可以看到 foo() 和 bar() 方法的第一行都被增加了一行代码,也就是这行代码对这两个方法实现了增强。
不仅如此,如果使用代理实现增强,被调用的 bar() 方法不会被成功增强,因为调用时默认使用了 this 关键字,表示调用的是原类中的方法,而不是代理类中的方法。
框架

基于代理(proxy)实现

JDK 动态代理

JDK 动态代理只能针对接口进行代理。

interface Foo {
        void foo();
    }

static final class Target implements Foo {
    @Override
    public void foo() {
        System.out.println("target foo");
    }
}
//目标对象
Target target = new Target();

classloader 是用于加载动态代理在运行期间由代理类生成的.class 文件(没有源码->字节码步骤):

ClassLoader classLoader = JdkProxyDemo.class.getClassLoader();

使用Proxy.newProxyInstance()方法创建代理实例,该方法接收三个参数:目标对象的类加载器、目标对象实现的接口数组,以及上述创建的InvocationHandler实例。

Foo proxy = (Foo) Proxy.newProxyInstance(classLoader, 
                                         new Class[]{Foo.class}, 
                                         (p, method, params) -> {
    System.out.println("before...");
    System.out.println("after...");
    // 也返回目标方法执行的结果
    return result;
});

这初步实现了对目标对象的增强功能,那么如何执行目标对象内部方法呢?
关注到方法InvocationHandler参数的第二、三个,method 代表的是当前代理类正在执行的方法对象,params 则是对应参数,那么我们使用反射调用即可获得调用该方法的目标对象利用参数执行的结果:

Object result = method.invoke(target, params);
proxy.foo();

运行结果:

before...
target foo
after...

:::info
代理对象和目标对象是兄弟关系,都实现了相同的接口,因此不能将代理对象强转成目标对象类型;
代理类与目标类之间没有继承关系,因此目标类可以被 final 修饰。
:::

模拟 JDK 动态代理

目标类:

interface Foo {
    void foo();
    void bar();
}

接下来,我们从命名开始模仿 JDK 的动态代理实现:

public class $Proxy0 extends Proxy implements Foo {

    @Override
    public void foo() {
    }

    @Override
    public void bar() {
    }
}

代理类已经实现,我们在启动类中创建代理类:

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

在代理类中,做有关增强实现:

public class $Proxy0 extends Proxy implements Foo {

    @Override
    public void foo() {
        //功能增强
        ...
        //调用目标方法
        new Target().foo();
    }

    @Override
    public void bar() {
        ...
    }
}

此时我们代理类中的增强和回调方法逻辑是写死的,要针对不同方法做不同处理,就要把这部分逻辑抽象出来,这里我们使用一个接口InvocationHandler 来实现,具体逻辑如下:

public $Proxy0(InvocationHandler h) {
    super(h);
}
public void foo() {
        try {
            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 {
            Object result = h.invoke(this, bar, new Object[0]);
            return (int) result;
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

在主方法中,传递有关参数:

Foo proxy = new $Proxy0(new InvocationHandler() {    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
        System.out.println("before...");
        return method.invoke(new Target(), args);
    }
});

这样,我们就可以针对不同的目标类中的方法进行不同的增强了。
完整代码如下:

public class A12 {

    interface Foo {
        void foo();
        void bar();
    }

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

        public void bar() {
            System.out.println("target bar");
            return 100;
        }
    }

    public static void main(String[] param) {
        // ⬇️1. 创建代理,这时传入 InvocationHandler
        Foo proxy = new $Proxy0(new InvocationHandler() {    
            // ⬇️5. 进入 InvocationHandler
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
                // ⬇️6. 功能增强
                System.out.println("before...");
                // ⬇️7. 反射调用目标方法
                return method.invoke(new Target(), args);
            }
        });
        // ⬇️2. 调用代理方法
        proxy.foo();
        proxy.bar();
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

// ⬇️这就是 jdk 代理类的源码, 秘密都在里面
public class $Proxy0 extends Proxy implements A12.Foo {

    public $Proxy0(InvocationHandler h) {
        super(h);
    }
    // ⬇️3. 进入代理方法
    public void foo() {
        try {
            // ⬇️4. 回调 InvocationHandler
            h.invoke(this, foo, new Object[0]);
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

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

上面的实现中目标对象的方法都是没有返回值的,在代理对象侧该如何获取方法的返回值呢?
首先改写我们的抽象处理类:InvocationHandler:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
    System.out.println("before...");
    return method.invoke(new Target(), args);
}
public int bar() {
        try {
            Object result = h.invoke(this, bar, new Object[0]);
            return (int) result;
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

以上代码还有优化的空间,每次获取方法时都声明了一个新变量,我们可以将它们提到静态代码块中,一次加载:

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

用 Arthas 工具反编译类:

/*
 * Decompiled with CFR.
 *
 * Could not load the following classes:
 *  com.itheima.a11.JdkProxyDemo$Foo
 */
package com.itheima.a11;

import com.itheima.a11.JdkProxyDemo;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

final class $Proxy0
extends Proxy
implements JdkProxyDemo.Foo {
    private static final Method m0;
    private static final Method m1;
    private static final Method m2;
    private static final Method m3;

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

    public final int hashCode() {
        try {
            return (Integer)this.h.invoke(this, m0, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final boolean equals(Object object) {
        try {
            return (Boolean)this.h.invoke(this, m1, new Object[]{object});
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final String toString() {
        try {
            return (String)this.h.invoke(this, m2, null);
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void foo() {
        try {
            this.h.invoke(this, m3, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    static {
        try {
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("com.itheima.a11.JdkProxyDemo$Foo").getMethod("foo", new Class[0]);
            return;
        }
        catch (NoSuchMethodException noSuchMethodException) {
            throw new NoSuchMethodError(noSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException classNotFoundException) {
            throw new NoClassDefFoundError(classNotFoundException.getMessage());
        }
    }

    private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup lookup) throws IllegalAccessException {
        if (lookup.lookupClass() == Proxy.class && lookup.hasFullPrivilegeAccess()) {
            return MethodHandles.lookup();
        }
        throw new IllegalAccessException(lookup.toString());
    }
}

收获💡

代理一点都不难,无非就是利用了多态、反射的知识

  1. 方法重写可以增强逻辑,只不过这【增强逻辑】千变万化,不能写死在代理内部
  2. 通过接口回调将【增强逻辑】置于代理类之外
  3. 配合接口方法反射(是多态调用),就可以再联动调用目标方法
  4. 会用 arthas 的 jad 工具反编译代理类
  5. 限制⛔:代理增强是借助多态来实现,因此成员变量、静态方法、final 方法均不能通过代理实现
JDK 代理类字节码生成

JDK 在生成代理类时,没有经历源码阶段、编译阶段,而是直接到字节码阶段,使用了 ASM 来完成。
ASM 的学习成本较高,在此不做过多介绍,本节将采用一直“曲线求国”的方式,使用 IDEA 的 ASM Bytecode outline 插件将 Java 源码转换成使用 ASM 编写的代码。
ASM Bytecode outline 插件在高版本 Java 中可能无法很好地工作,建议在 Java8 环境下使用。
自行编写一个接口和代理类:

public interface Foo {
    void foo();
}

public class $Proxy0 extends Proxy implements Foo {
    private static final long serialVersionUID = 6059465134835974286L;

    static Method foo;

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

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

    @Override
    public void foo() {
        try {
            this.h.invoke(this, foo, null);
        } catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
}

将上述代码进行编译,编译成功后在 Proxy0Dump 中:

import jdk.internal.org.objectweb.asm.*;

/**
 * @author mofan
 * @date 2023/1/16 22:04
 */
public class $Proxy0Dump implements Opcodes {

    public static byte[] dump() throws Exception {

        ClassWriter cw = new ClassWriter(0);
        FieldVisitor fv;
        MethodVisitor mv;
        AnnotationVisitor av0;

        // 生成类信息
        cw.visit(52, ACC_PUBLIC + ACC_SUPER, "indi/mofan/$Proxy0", null, "java/lang/reflect/Proxy", new String[]{"indi/mofan/Foo"});

        cw.visitSource("$Proxy0.java", null);

        {
            fv = cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, "serialVersionUID", "J", null, new Long(6059465134835974286L));
            fv.visitEnd();
        }
        {
            fv = cw.visitField(ACC_STATIC, "foo", "Ljava/lang/reflect/Method;", null, null);
            fv.visitEnd();
        }
        {
            mv = cw.visitMethod(ACC_PROTECTED, "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", null, null);
            mv.visitParameter("h", 0);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(26, l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitVarInsn(ALOAD, 1);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/reflect/Proxy", "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", false);
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLineNumber(27, l1);
            mv.visitInsn(RETURN);
            Label l2 = new Label();
            mv.visitLabel(l2);
            mv.visitLocalVariable("this", "Lindi/mofan/$Proxy0;", null, l0, l2, 0);
            mv.visitLocalVariable("h", "Ljava/lang/reflect/InvocationHandler;", null, l0, l2, 1);
            mv.visitMaxs(2, 2);
            mv.visitEnd();
        }
        {
            mv = cw.visitMethod(ACC_PUBLIC, "foo", "()V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            Label l1 = new Label();
            Label l2 = new Label();
            mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Throwable");
            mv.visitLabel(l0);
            mv.visitLineNumber(32, l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, "indi/mofan/$Proxy0", "h", "Ljava/lang/reflect/InvocationHandler;");
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETSTATIC, "indi/mofan/$Proxy0", "foo", "Ljava/lang/reflect/Method;");
            mv.visitInsn(ACONST_NULL);
            mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true);
            mv.visitInsn(POP);
            mv.visitLabel(l1);
            mv.visitLineNumber(35, l1);
            Label l3 = new Label();
            mv.visitJumpInsn(GOTO, l3);
            mv.visitLabel(l2);
            mv.visitLineNumber(33, l2);
            mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/Throwable"});
            mv.visitVarInsn(ASTORE, 1);
            Label l4 = new Label();
            mv.visitLabel(l4);
            mv.visitLineNumber(34, l4);
            mv.visitTypeInsn(NEW, "java/lang/reflect/UndeclaredThrowableException");
            mv.visitInsn(DUP);
            mv.visitVarInsn(ALOAD, 1);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/reflect/UndeclaredThrowableException", "<init>", "(Ljava/lang/Throwable;)V", false);
            mv.visitInsn(ATHROW);
            mv.visitLabel(l3);
            mv.visitLineNumber(36, l3);
            mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
            mv.visitInsn(RETURN);
            Label l5 = new Label();
            mv.visitLabel(l5);
            mv.visitLocalVariable("throwable", "Ljava/lang/Throwable;", null, l4, l3, 1);
            mv.visitLocalVariable("this", "Lindi/mofan/$Proxy0;", null, l0, l5, 0);
            mv.visitMaxs(4, 2);
            mv.visitEnd();
        }
        {
            mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            Label l1 = new Label();
            Label l2 = new Label();
            mv.visitTryCatchBlock(l0, l1, l2, "java/lang/NoSuchMethodException");
            mv.visitLabel(l0);
            mv.visitLineNumber(19, l0);
            mv.visitLdcInsn(Type.getType("Lindi/mofan/Foo;"));
            mv.visitLdcInsn("foo");
            mv.visitInsn(ICONST_0);
            mv.visitTypeInsn(ANEWARRAY, "java/lang/Class");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false);
            mv.visitFieldInsn(PUTSTATIC, "indi/mofan/$Proxy0", "foo", "Ljava/lang/reflect/Method;");
            mv.visitLabel(l1);
            mv.visitLineNumber(22, l1);
            Label l3 = new Label();
            mv.visitJumpInsn(GOTO, l3);
            mv.visitLabel(l2);
            mv.visitLineNumber(20, l2);
            mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/NoSuchMethodException"});
            mv.visitVarInsn(ASTORE, 0);
            Label l4 = new Label();
            mv.visitLabel(l4);
            mv.visitLineNumber(21, l4);
            mv.visitTypeInsn(NEW, "java/lang/NoSuchMethodError");
            mv.visitInsn(DUP);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/NoSuchMethodException", "getMessage", "()Ljava/lang/String;", false);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/NoSuchMethodError", "<init>", "(Ljava/lang/String;)V", false);
            mv.visitInsn(ATHROW);
            mv.visitLabel(l3);
            mv.visitLineNumber(23, l3);
            mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
            mv.visitInsn(RETURN);
            mv.visitLocalVariable("e", "Ljava/lang/NoSuchMethodException;", null, l4, l3, 0);
            mv.visitMaxs(3, 1);
            mv.visitEnd();
        }
        cw.visitEnd();

        return cw.toByteArray();
    }
}

编写测试方法使用 Proxy0Dump 生成 Proxy0 的 class 文件:

public class TestProxy {
    public static void main(String[] args) throws Exception {
        byte[] dump = $Proxy0Dump.dump();

        FileOutputStream os = new FileOutputStream("$Proxy0.class");
        os.write(dump, 0, dump.length);
        os.close();
    }
}

运行 main() 方法后,在工作目录下生成 Proxy0.class 文件,IDEA 反编译后的内容与手动编写的 Proxy0.java 文件的内容无异。实际使用时并不需要使用 Proxy0Dump 生成 $Proxy.class 文件,而是利用 ClassLoader 直接加载类信息:

public static void main(String[] args) throws Exception {
    byte[] dump = $Proxy0Dump.dump();

    ClassLoader classLoader = new ClassLoader() {
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            return super.defineClass(name, dump, 0, dump.length);
        }
    };

    Class<?> proxyClass = classLoader.loadClass("indi.mofan.$Proxy0");
    Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
    Foo fooProxy = (Foo) constructor.newInstance((InvocationHandler) (proxy, method, args1) -> {
        System.out.println("before...");
        System.out.println("模拟调用目标");
        return null;
    });

    fooProxy.foo();
}
JDK 反射优化

相比于正常调用方法,利用反射的性能要稍微低一些,JDK 有怎么反射进行优化吗?我们通过以下程序来测试一下:

/**
 * @author mofan
 * @date 2023/1/16 22:36
 */
public class TestMethodProxy {

    public static void main(String[] args) throws Exception {
        Method foo = TestMethodProxy.class.getMethod("foo", int.class);
        for (int i = 1; i <= 17; i++) {
            show(i, foo);
            foo.invoke(null, i);
        }
        System.in.read();
    }

    // 方法反射调用时,底层使用了 MethodAccessor 的实现类
    private static void show(int i, Method foo) throws Exception {
        Method getMethodAccessor = Method.class.getDeclaredMethod("getMethodAccessor");
        getMethodAccessor.setAccessible(true);
        Object invoke = getMethodAccessor.invoke(foo);
        if (invoke == null) {
            System.out.println(i + ":" + null);
            return;
        }
        // DelegatingMethodAccessorImpl 的全限定类名(不同版本的 JDK 存在差异)
        Field delegate = Class.forName("sun.reflect.DelegatingMethodAccessorImpl").getDeclaredField("delegate");
        delegate.setAccessible(true);
        System.out.println(i + ": " + delegate.get(invoke));
    }

    public static void foo(int i) {
        System.out.println(i + ": foo");
    }
}

第一次调用时没有使用 MethodAccessor 对象,从第二次到第十六次,使用了 NativeMethodAccessorImpl 对象,而在第十七次使用了 GeneratedMethodAccessor2 对象。
NativeMethodAccessorImpl 基于 Java 本地 API 实现,性能较低,第十七次调用换成 GeneratedMethodAccessor2 后,性能得到一定的提升。
使用 Arthas 反编译查看 GeneratedMethodAccessor2 类中的信息,内容如下:

public class GeneratedMethodAccessor2 extends MethodAccessorImpl {
    /*
     * Loose catch block
     */
    public Object invoke(Object object, Object[] objectArray) throws InvocationTargetException {
        // --snip--
        try {
            // 正常调用方法
            TestMethodProxy.foo((int)c);
            return null;
        }
        catch (Throwable throwable) {
            throw new InvocationTargetException(throwable);
        }
        catch (ClassCastException | NullPointerException runtimeException) {
            throw new IllegalArgumentException(super.toString());
        }
    }
}

在反编译得到的代码中,不再是通过反射调用方法,而是直接正常调用方法,即:

TestMethodProxy.foo((int)c);

CGlib 动态代理

与 JDK 提供的动态代理方式不同的是,CGlib 采用的是Enhancer 来进行的代理:

Target proxy = (Target) Enhancer.create(Target.class, (MethodInterceptor) (p, method, args, methodProxy) -> {
    System.out.println("before...");
    Object result = methodProxy.invokeSuper(p, args); 
    System.out.println("after...");
    return result;
});

注意到MethodInterceptor 的第四个参数,这是 CGlib 提供的调用目标方法的独特方法:

// 内部没有用反射, 需要目标 (spring使用就是这种)
Object result = methodProxy.invoke(target, args); 
// 内部没有用反射, 需要代理
Object result = methodProxy.invokeSuper(p, args); 

methodProxy 可以避免反射调用,相较于反射调用性能更优。

CGlib 是基于继承目标对象实现的,所以目标对象不能用 final 修饰,目标对象的方法也不能,否则代理失效

模拟 CGlib 动态代理

同样先模拟下 CGLib 动态代理的模拟:
被代理的类,即目标类:

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

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

    public void save(long i) {
        System.out.println("save(long)");
    }
}
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 o, 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);
}
MethodProxy

在 JDK 的动态代理中,使用反射对方法进行调用,而在 CGLib 动态代理中,可以使用 intercept() 方法中 MethodProxy 类型的参数实现不经过反射来调用方法。
接收的 MethodProxy 类型的参数可以像 Method 类型的参数一样,在静态代码块中被实例化。
可以通过静态方法 MethodProxy.create() 来创建 MethodProxy 对象:

public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
   MethodProxy proxy = new MethodProxy();
   proxy.sig1 = new Signature(name1, desc);
   proxy.sig2 = new Signature(name2, desc);
   proxy.createInfo = new CreateInfo(c1, c2);
   return proxy;
}
  • 参数 c1 指目标类(或者说原始类)的 Class 对象;
  • 参数 c2 指代理类的 Class 对象;
  • 参数 desc 指方法描述符(【Lambda 与序列化】一文中介绍了关于 Java 描述符的更多内容);
  • 参数 name1 指带 增强 功能的方法名称;
  • 参数 name2 指带 原始 功能的方法名称。
/**
 * @author mofan
 * @date 2023/1/18 21:31
 */
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 MethodProxy save0Proxy;
    static MethodProxy save1Proxy;
    static MethodProxy save2Proxy;

    static {
        try {
            save0 = Target.class.getMethod("save");
            save1 = Target.class.getMethod("save", int.class);
            save2 = Target.class.getMethod("save", long.class);

            save0Proxy = MethodProxy.create(Target.class, Proxy.class, "()V", "save", "saveSuper");
            save1Proxy = MethodProxy.create(Target.class, Proxy.class, "(I)V", "save", "saveSuper");
            save2Proxy = MethodProxy.create(Target.class, Proxy.class, "(J)V", "save", "saveSuper");
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }

    // >>>>>>>>>>>>>>>>>>>>>>>> 带原始功能的方法
    public void saveSuper() {
        super.save();
    }

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

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


    // >>>>>>>>>>>>>>>>>>>>>>>> 带增强功能的方法
    @Override
    public void save() {
        try {
            methodInterceptor.intercept(this, save0, new Object[0], save0Proxy);
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

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

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

MethodProxy 原理

调用 methodProxy.invoke() 方法时,会额外使用一个代理类,该代理类配合目标对象使用。
调用 methodProxy.invokeSuper() 方法时,也会额外使用一个代理类,该代理类配合代理对象使用。
当调用 MethodProxy 对象的 invoke() 方法或 invokeSuper() 方法时,就会生成这两个代理类,它们都继承至 FastClass。
FastClass 是一个抽象类,其内部有多个抽象方法:

public abstract class FastClass {
    public abstract int getIndex(String var1, Class[] var2);

    public abstract int getIndex(Class[] var1);

    public abstract Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException;

    public abstract Object newInstance(int var1, Object[] var2) throws InvocationTargetException;

    public abstract int getIndex(Signature signature);

    public abstract int getMaxIndex();
}

重点讲解 invoke() 方法与 getIndex(Signature signature) 方法。

模拟生成的与目标类相关的代理类
public class TargetFastClass {

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

    /**
     * <p>获取目标方法的编号</p>
     * <p>
     * Target 目标类中的方法:
     * save()             0
     * save(int)          1
     * save(long)         2
     * </p>
     *
     * @param signature 包含方法名称、参数返回值
     * @return 方法编号
     */
    public int getIndex(Signature signature) {
        if (s0.equals(signature)) {
            return 0;
        }
        if (s1.equals(signature)) {
            return 1;
        }
        if (s2.equals(signature)) {
            return 2;
        }
        return -1;
    }

    /**
     * 根据 getIndex() 方法返回的方法编号正常调用目标对象方法
     *
     * @param index       方法编号
     * @param target       目标对象
     * @param args 调用目标对象方法需要的参数
     * @return 方法返回结果
     */
    public Object invoke(int index, Object target, Object[] args) {
        if (index == 0) {
            ((Target) target).save();
            return null;
        }
        if (index == 1) {
            ((Target) target).save((int) args[0]);
            return null;
        }
        if (index == 2) {
            ((Target) target).save((long) args[0]);
            return null;
        }
        throw new RuntimeException("无此方法");
    }

    public static void main(String[] args) {
        TargetFastClass fastClass = new TargetFastClass();
        int index = fastClass.getIndex(new Signature("save", "()V"));
        fastClass.invoke(index, new Target(), new Object[0]);

        index = fastClass.getIndex(new Signature("save", "(J)V"));
        fastClass.invoke(index, new Target(), new Object[]{2L});
    }
}
模拟生成的与代理类相关的代理类
public class ProxyFastClass {
    static Signature s0 = new Signature("saveSuper", "()V");
    static Signature s1 = new Signature("saveSuper", "(I)V");
    static Signature s2 = new Signature("saveSuper", "(J)V");

    /**
     * <p>获取代理方法的编号</p>
     * <p>
     * Proxy 代理类中的方法:
     * saveSuper()             0
     * saveSuper(int)          1
     * saveSuper(long)         2
     * </p>
     *
     * @param signature 包含方法名称、参数返回值
     * @return 方法编号
     */
    public int getIndex(Signature signature) {
        if (s0.equals(signature)) {
            return 0;
        }
        if (s1.equals(signature)) {
            return 1;
        }
        if (s2.equals(signature)) {
            return 2;
        }
        return -1;
    }

    /**
     * 根据 getIndex() 方法返回的方法编号正常调用代理对象中带原始功能的方法
     *
     * @param index 方法编号
     * @param proxy 代理对象
     * @param args  调用方法需要的参数
     * @return 方法返回结果
     */
    public Object invoke(int index, Object proxy, Object[] args) {
        if (index == 0) {
            ((Proxy) proxy).saveSuper();
            return null;
        }
        if (index == 1) {
            ((Proxy) proxy).saveSuper((int) args[0]);
            return null;
        }
        if (index == 2) {
            ((Proxy) proxy).saveSuper((long) args[0]);
            return null;
        }
        throw new RuntimeException("无此方法");
    }

    public static void main(String[] args) {
        ProxyFastClass fastClass = new ProxyFastClass();
        int index = fastClass.getIndex(new Signature("saveSuper", "()V"));
        fastClass.invoke(index, new Proxy(), new Object[0]);
    }
}

调用 MethodProxy.create() 方法创建 MethodProxy 对象时,要求传递带增强功能的方法名称、带原始功能的方法名称以及方法描述符。
根据两个方法名称和方法描述符可以在调用生成的两个代理类中的 getIndex() 方法时获取被增强方法的编号,之后:

  • 调用 methodProxy.invoke() 方法时,就相当于调用 TargetFastClass 中的 invoke() 方法,并在这个 invoke() 方法中正常调用目标对象方法(Spring 底层的选择)。
  • 调用 methodProxy.invokeSuper() 方法时,就相当于调用 ProxyFastClass 中的 invoke() 方法,并在这个 invoke() 方法中正常调用代理对象中带原始功能的方法。

JDK 和 CGLib 的统一

切面有 aspect 和 advisor 两个概念,aspect 是多组通知(advice)和切点(pointcut)的组合,也是实际编码时使用的,advisor 则是更细粒度的切面,仅包含一个通知和切点,aspect 在生效之前会被拆解成多个 advisor。
在 Spring 中,切点通过接口 org.springframework.aop.Pointcut 来表示:

public interface Pointcut {

    /**
     * 根据类型过滤
     */
    ClassFilter getClassFilter();

    /**
     * 根据方法匹配
     */
    MethodMatcher getMethodMatcher();


    /**
     * Canonical Pointcut instance that always matches.
     */
    Pointcut TRUE = TruePointcut.INSTANCE;

}

Pointcut 接口有很多实现类,比如:

  • AnnotationMatchingPointcut:通过注解进行匹配
  • AspectJExpressionPointcut:通过 AspectJ 表达式进行匹配(本节的选择)

在 Spring 中,通知的表示也有很多接口,在此介绍最基本、最重要的接口 org.aopalliance.intercept.MethodInterceptor,这个接口实现的通知属于环绕通知。
在 Spring 中,切面的实现也有很多,在此选择 DefaultPointcutAdvisor,创建这种切面时,传递一个节点和通知。
最后创建代理对象时,无需显式实现 JDK 动态代理或 CGLib 动态代理,Spring 提供了名为 ProxyFactory 的工厂,其内部通过不同的情况选择不同的代理实现,更方便地创建代理对象。

interface I1 {
    void foo();

    void bar();
}

static class Target1 implements I1 {

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

    @Override
    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");
    }
}

public static void main(String[] args) {
    /*
     * 两个切面概念:
     *  aspect =
     *          通知 1 (advice) + 切点 1(pointcut)
     *          通知 2 (advice) + 切点 2(pointcut)
     *          通知 3 (advice) + 切点 3(pointcut)
     *          ...
     *
     * advisor = 更细粒度的切面,包含一个通知和切点
     * */

    // 1. 备好切点(根据 AspectJ 表达式进行匹配)
    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    pointcut.setExpression("execution(* foo())");
    // 2. 备好通知
    MethodInterceptor advice = invocation -> {
        System.out.println("before...");
        Object result = invocation.proceed();
        System.out.println("after...");
        return result;
    };
    // 3. 备好切面
    DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
    // 4. 创建代理
    Target1 target = new Target1();
    ProxyFactory factory = new ProxyFactory();
    factory.setTarget(target);
    factory.addAdvisor(advisor);

    I1 proxy = (I1) factory.getProxy();
    System.out.println(proxy.getClass());
    proxy.foo();
    proxy.bar();
}

Spring 是根据什么信息来选择不同的动态代理实现呢?
ProxyFactory 的父类 ProxyConfig 中有个名为 proxyTargetClass 的布尔类型成员变量:

  • 当 proxyTargetClass == false,并且目标对象所在类实现了接口时,将选择 JDK 动态代理;
  • 当 proxyTargetClass == false,但目标对象所在类未实现接口时,将选择 CGLib 动态代理;
  • 当 proxyTargetClass == true,总是选择 CGLib 动态代理。

上文中的 target 对象的所在类 Targer1 实现了 I1 接口,最终为什么依旧选择了 CGLib 动态代理作为代理类的创建方式呢?
这是因为并没有显式这是 target 对象的实现类,Spring 认为其并未实现接口。
设置 factory 对象的 interfaces 信息:

factory.setInterfaces(target.getClass().getInterfaces());

再设置 factory 对象的 proxyTargetClass 为 true:

factory.setProxyTargetClass(true);
PixPin_2024-08-17_15-41-05.png

切点匹配

上一节中,选择 AspectJExpressionPointcut 作为切点的实现,判断编写的 AspectJ 表达式是否与某一方法匹配可以使用其 matches() 方法。
我们经常使用@Transactional 来对事务进行增强,这个注解内部也是用的AspectJExpressionPointcut 与 @annotation() 切点表达式相结合对目标方法进行匹配的吗?
我们来测试一下:

AspectJExpressionPointcut pt2 = new AspectJExpressionPointcut();
pt2.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)");
System.out.println(pt2.matches(T2.class.getMethod("foo"), T2.class));
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() {
        }
    }

发现并没有匹配成功,这是因为AspectJExpressionPointcut 只提供了根据方法匹配的切点,而我们的@Transactional 注解除了可以作用在方法上,还可以作用在类(或接口)上。
在底层 @Transactional 注解的匹配使用到了 StaticMethodMatcherPointcut,在此模拟一下:

        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, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);
                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));
        System.out.println(pt3.matches(T3.class.getMethod("foo"), T3.class));

无论是 AspectJExpressionPointcut 还是 StaticMethodMatcherPointcut,它们都实现了 MethodMatcher 接口,用来执行方法的匹配。
image.png

从@Aspect 到@Adviser

@Aspect 高级切面实现:

@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...");
    }
}

Adviser 低级切面类实现:

@Configuration
static class Config {
    /**
     * 低级切面,由一个切点和一个通知组成
     */
    @Bean
    public Advisor advisor3(MethodInterceptor advice3) {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* foo())");
        return new DefaultPointcutAdvisor(pointcut, advice3);
    }

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

注册ConfigurationClassPostProcessor,以解析@Bean 注解

aspect1
config
org.springframework.context.annotation.ConfigurationClassPostProcessor
advisor3
advices

Spring 中存在一个名为 AnnotationAwareAspectJAutoProxyCreator 的 Bean 后置处理器,尽管它的名称中没有 BeanPostProcessor 的字样,但它确实是实现了 BeanPostProcessor 接口的。
AnnotationAwareAspectJAutoProxyCreator 有两个主要作用:

  1. 找到容器中所有的切面,针对高级切面,将其转换为低级切面;
  2. 根据切面信息,利用 ProxyFactory 创建代理对象。

AnnotationAwareAspectJAutoProxyCreator 实现了 BeanPostProcessor,可以在 Bean 生命周期中的一些阶段对 Bean 进行拓展。AnnotationAwareAspectJAutoProxyCreator 可以在 Bean 进行 依赖注入之前、Bean 初始化之后 对 Bean 进行拓展。
重点介绍 AnnotationAwareAspectJAutoProxyCreator 中的两个方法:

  • findEligibleAdvisors():位于父类 AbstractAdvisorAutoProxyCreator 中,用于找到符合条件的切面类。低级切面直接添加,高级切面转换为低级切面再添加。
  • wrapIfNecessary():位于父类 AbstractAutoProxyCreator 中,用于将有资格被代理的 Bean 进行包装,即创建代理对象。

findEligibleAdvisors() 方法

findEligibleAdvisors() 方法接收两个参数:

  • beanClass:配合切面使用的目标类 Class 信息
  • beanName:当前被代理的 Bean 的名称
// 测试 findEligibleAdvisors 方法
AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
// 获取能够配合 Target1 使用的切面
List<Advisor> advisors = creator.findEligibleAdvisors(Target1.class, "target1");
advisors.forEach(System.out::println);
org.springframework.aop.interceptor.ExposeInvocationInterceptor.ADVISOR
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.framework.autoproxy.A17$Config$$Lambda$56/802243390@7bd4937b]
InstantiationModelAwarePointcutAdvisor: expression [execution(* foo())]; advice method [public void org.springframework.aop.framework.autoproxy.A17$Aspect1.before()]; perClauseKind=SINGLETON
InstantiationModelAwarePointcutAdvisor: expression [execution(* foo())]; advice method [public void org.springframework.aop.framework.autoproxy.A17$Aspect1.after()]; perClauseKind=SINGLETON

打印出 4 个能配合 Target1 使用的切面信息,其中:

  • 第一个切面 ExposeInvocationInterceptor.ADVISOR 是 Spring 为每个代理对象都会添加的切面;
  • 第二个切面 DefaultPointcutAdvisor 是自行编写的低级切面;
  • 第三个和第四个切面 InstantiationModelAwarePointcutAdvisor 是由高级切面转换得到的两个低级切面。

wrapIfNecessary() 方法

wrapIfNecessary() 方法内部调用了 findEligibleAdvisors() 方法,若 findEligibleAdvisors() 方法返回的集合不为空,则表示需要创建代理对象。
如果需要创建对象,wrapIfNecessary() 方法返回的是代理对象,否则仍然是原对象。
wrapIfNecessary() 方法接收三个参数:

  • bean:原始 Bean 实例
  • beanName:Bean 的名称
  • cacheKey:用于元数据访问的缓存 key
public static void main(String[] args) {
    // --snip--

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

    context.close();
}
class org.springframework.aop.framework.autoproxy.A17$Target1$$EnhancerBySpringCGLIB$$634976f6
class org.springframework.aop.framework.autoproxy.A17$Target2

切面顺序控制

针对高级切面来说,可以在类上使用 @Order 注解,比如:

@Aspect
@Order(1)
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...");
    }
}

在高级切面中,@Order 只有放在类上才生效,放在方法上不会生效。比如高级切面中有多个前置通知,这些前置通知对应的方法上使用 @Order 注解是无法生效的。
针对低级切面,需要设置 advisor 的 order 值,而不是向高级切面那样使用 @Order 注解,使用 @Order 注解设置在 advisor3() 方法上是无用的:

@Configuration
static class Config {
    /**
     * 低级切面,由一个切点和一个通知组成
     */
    @Bean
    public Advisor advisor3(MethodInterceptor advice3) {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* foo())");
        DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice3);
        // 设置切面执行顺序
        advisor.setOrder(2);
        return advisor;
    }

    // --snip--
}

代理对象创建时机

代理对象的创建时机:

  • 无循环依赖时,在 Bean 初始化阶段之后创建;
  • 有循环依赖时,在 Bean 实例化后、依赖注入之前创建,并将代理对象暂存于二级缓存。

Bean 的依赖注入阶段和初始化阶段不应该被增强,仍应被施加于原始对象。

高级切面与低级切面的转换

调用 AnnotationAwareAspectJAutoProxyCreator 对象的 findEligibleAdvisors() 方法时,获取能配合目标 Class 使用的切面,最终返回 Advisor 列表。在搜索过程中,如果遇到高级切面,则会将其转换成低级切面。
以解析 @Before 注解为例:

public static void main(String[] args) throws Throwable {
    // 切面对象实例工厂,用于后续反射调用切面中的方法
    AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());
    // 高级切面转低级切面类
    List<Advisor> list = new ArrayList<>();
    for (Method method : Aspect.class.getDeclaredMethods()) {
        if (method.isAnnotationPresent(Before.class)) {
            // 解析切点
            String expression = method.getAnnotation(Before.class).value();
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression(expression);
            // 通知类。前置通知对应的通知类是 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);
    }
}

@Before 标记的前置通知会被转换成原始的 AspectJMethodBeforeAdvice 形式,该对象包含了以下信息:

  • 通知对应的方法信息
  • 切点信息
  • 通知对象如何创建,本例公用一个 Aspect 对象

通知相关注解与原始通知类对应关系如下:

注解 对应的原始通知类
@BeforeAspectJMethodBeforeAdvice
@AfterReturningAspectJAfterReturningAdvice
@AfterThrowingAspectJAfterThrowingAdvice
@AfterAspectJAfterAdvice
@AroundAspectJAroundAdvice

静态通知调用

为了实现类似递归的调用顺序,Spring 底层将所有类型的通知都转换为了统一的MethodInterceptor。

|-> before1 ----------------------------------- 从当前线程获取 MethodInvocation
|                                             |
|   |-> before2 --------------------          | 从当前线程获取 MethodInvocation
|   |                              |          |
|   |   |-> target ------ 目标   advice2    advice1
|   |                              |          |
|   |-> after2 ---------------------          |
|                                             |
|-> after1 ------------------------------------

通知相关注解都对应一个原始通知类,在 Spring 底层会将这些通知转换成环绕通知 MethodInterceptor。如果原始通知类本就实现了 MethodInterceptor 接口,则无需转换。

原始通知类是否需要转换成 MethodInterceptor
AspectJMethodBeforeAdvice
AspectJAfterReturningAdvice
AspectJAfterThrowingAdvice
AspectJAfterAdvice
AspectJAroundAdvice

使用 ProxyFactory 无论基于哪种方式创建代理对象,最终调用 advice(通知,或者说通知对应的方法)的都是 MethodInvocation 对象。
项目中存在的 advisor(原本的低级切面和由高级切面转换得到的低级切面)往往不止一个,它们一个套一个地被调用,因此需要一个调用链对象,即 MethodInvocation。
MethodInvocation 需要知道 advice 有哪些,还需要知道目标对象是哪个。调用次序如下:
通知调用次序.png
统一转换成环绕通知的形式,体现了设计模式中的适配器模式:

  • 对外更方便使用和区分各种通知类型
  • 对内统一都是环绕通知,统一使用 MethodInterceptor 表示

通过 ProxyFactory 对象的 getInterceptorsAndDynamicInterceptionAdvice() 方法将其他通知统一转换为 MethodInterceptor 环绕通知。
转换得到的通知都是静态通知,体现在 getInterceptorsAndDynamicInterceptionAdvice() 方法中的 Interceptors 部分,这些通知在被调用时无需再次检查切点,直接调用即可。
测试代码:

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

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

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

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

    @AfterThrowing("execution(* foo())")
    public void afterThrowing(Exception e) {
        System.out.println("afterThrowing " + e.getMessage());
    }

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

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

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

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

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

    @AfterThrowing("execution(* foo())")
    public void afterThrowing(Exception e) {
        System.out.println("afterThrowing " + e.getMessage());
    }

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

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

调用链执行

高级切面成功转换成低级切面,切面中的通知也全部转换成环绕通知 MethodInterceptor,最后还要调用这些通知和目标方法。
这个调用交由调用链对象 MethodInvocation 来完成,在调用链对象中存放了所有经过转换得到的环绕通知和目标方法。
MethodInvocation 是一个接口,其最根本的实现是 ReflectiveMethodInvocation。
构建 ReflectiveMethodInvocation 对象需要 6 个参数:

  • proxy:代理对象
  • target:目标对象
  • method:目标对象中的方法对象
  • arguments:调用目标对象中的方法需要的参数
  • targetClass:目标对象的 Class 对象
  • interceptorsAndDynamicMethodMatchers:转换得到的环绕通知列表

调用链在执行过程会调用到很多通知,而某些通知内部可能需要使用调用链对象。因此需要将调用链对象存放在某一位置,使所有通知都能获取到调用链对象。
这个“位置”就是 当前线程。
那怎么将调用链对象放入当前线程呢?
可以在所有通知的最外层再添加一个环绕通知,其作用是将调用链对象放入当前线程。
将MethodInvocation放入当前线程.png
可以使用 Spring 提供的 ExposeInvocationInterceptor 作为最外层的环绕通知。

动态通知调用

前文的示例都是静态通知调用,无需参数绑定,执行时无需切点信息,性能较高。 相应地就有动态通知调用,它需要参数绑定,执行时还需要切点信息,性能较低。比如:

 @Aspect
 static class MyAspect {
     /**
      * 静态通知调用,无需参数绑定,性能较高
      * 执行时无需切点信息
      */
     @Before("execution(* foo(..))")
     public void before1() {
         System.out.println("before1");
    }
 ​
     /**
      * 动态通知调用,需要参数绑定,性能较低
      * 执行时还需要切点信息
      */
     @Before("execution(* foo(..)) && args(x)")
     public void before2(int x) {
         System.out.printf("before(%d)\n", x);
    }
 }

此作者没有提供个人介绍
最后更新于 2024-08-22