Spring -- AOP

1. AOP 实现方式

1.1 ajc编译器

使用aspectj编译器插件来改动class类文件来实现增强(在编译阶段)
编译器增强能突破代理仅能通过方法重写增强的限制:可以对构造方法、静态方法等实现增强

先写一下代码:

<dependencies>
	<dependency>
		<groupId>org.aspectj</groupId>
		<artifactId>aspectjweaver</artifactId>
	</dependency>
	
	<dependency>
		<groupId>org.aspectj</groupId>
		<artifactId>aspectjrt</artifactId>
	</dependency>
 </dependencies>

<build>
    <plugins>
		<plugin>
		    <groupId>org.codehaus.mojo</groupId>
		    <artifactId>aspectj-maven-plugin</artifactId>
		    <version>1.14.0</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>
		                <goal>compile</goal>
		                <goal>test-compile</goal>
		            </goals>
		        </execution>
		    </executions>
		</plugin>
	</plugins>
</build>
@Service
public class MyService {
	private static final Logger log = LoggerFactory.getLogger(MyService.class);
	
    public void foo() {
        log.debug("foo()");
    }
}
@Aspect  // 注意:此切面并未被 Spring 管理
public class MyAspect {
    private static final Logger log = LoggerFactory.getLogger(MyAspect.class);
    
    @Before("execution(* com.example.spring.a09.MyService.foo())")
    public void before() {
        log.debug("before()");
    }
}
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class A09Application {
    private static final Logger log = LoggerFactory.getLogger(MyAspect.class);
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(A09Application.class, args);
        MyService myService = context.getBean(MyService.class);
        log.debug("service class:{}", myService.getClass());
        myService.foo();

        context.close();
    }
}

注:
1. 因为idea 不会调用 ajc 编译器,所以一定要用 maven 的 compile 来编译
2. 不要使用 lombok 相关的注解,因为 ajc 不会编译,可能还会报错,使用 @Slf4j 时,会报错 0 log cannot be resolved

我们运行代码看一下输出结果

在这里插入图片描述
可以看到,foo() 方法已经被增强了,执行了 before(),但是请注意看,我们打印了 MyService 类型,并不是经过代理的类型

那说明一个问题,AOP 的实现方式,并不是只有代理方式一种

其实代码中时用的 aspect 编译器进行了增强,编译器的原理就是将 MyService 类的代码修改了,虽然源代码中 foo() 方法只有一行代码,但是编译后的 class 其实已经加入了增强代码,那我们看一下编译后的 class 文件

在这里插入图片描述
那我们在想一下,既然编译的 class 都被改写了,那和 Spring 就没有关系了,而且在 MyAspect 类上并没有加入 @Component 注解
那我们修改以下代码,将 Spring 的代码干掉

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class A09Application {
    private static final Logger log = LoggerFactory.getLogger(MyAspect.class);
    public static void main(String[] args) {
//        ConfigurableApplicationContext context = SpringApplication.run(A09Application.class, args);
//        MyService myService = context.getBean(MyService.class);
//        log.debug("service class:{}", myService.getClass());
//        myService.foo();
//
//        context.close();
        
        new MyService().foo();
    }
}

在这里插入图片描述

如果目标方式是静态方法,代理方式是无法增强的,但是 ajc 是可以增强的

@Service
public class MyService {

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

在这里插入图片描述
在这里插入图片描述

1.2 agnent 类加载

运行时需要在 VM options 里加入-javaagent:(maven仓库地址)/org/aspectj/aspectjweaver/1.9.7/aspectjweaver-1.9.7.jar

类加载时修改 .class 文件实现增强

1.3 proxy

1.3.1 JDK 代理

import java.lang.reflect.Proxy;

public class JdkProxyDemo {

    interface Foo {
        void foo();
    }

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

    public static void main(String[] param) {
        Target target = new Target();

        ClassLoader loader = JdkProxyDemo.class.getClassLoader();
        Foo proxy = (Foo) Proxy.newProxyInstance(loader, new Class[]{Foo.class}, (p, method, args) -> {
            System.out.println("before ...");
            Object result = method.invoke(target, args);
            System.out.println("after");
            return result;
        });

        proxy.foo();
    }
}

总结:
只能对接口代理,代理对象不可强转成目标对象
目标类可以定位 final

原理
  1. 先用代码简单模拟一下JDK动态代理。先定义一个Foo接口,里面有一个foo方法,再定义一个Target类来实现这个接口,代码如下:
public class A12 {

    interface Foo {
        void foo();
    }

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

}

接下来对 Target 类中的 foo() 方法进行增强

首先想再定义一个类实现 Foo 接口,然后在 foo() 中编写增强代码,接着再 new 一个 Target 对象,调用它的 foo() 方法,代码如下所示:

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

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

    }
  1. 上面的代码把功能增强的代码和调用目标的代码都固定在了代理类的内部,不太灵活。
    因此可以通过定义一个InvocationHandler接口的方式来将这部分代码解耦出来,代码如下:
interface InvocationHandler {
        void invoke();
    }

    static class $Proxy0 implements Foo {
        private InvocationHandler h;

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

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

    }
  1. 虽然将功能增强的代码和调用目标的代码通过接口的方式独立出来了,但是如果此时接口中新增了一个方法 bar()Target 类和 $Proxy0 类中都要实现 bar() 方法,那么调用 proxyfoo()bar() 方法都将间接调用目标对象的 foo() 方法,因为在 InvocationHandlerinvoke() 方法实现中调用的是 target.foo() 方法
    在这里插入图片描述在这里插入图片描述
  2. 改进为代理类中调用方法的时候,通过反射把接口中对应的方法 Method 对象作为参数传给 InvocationHandler,这样就可以通过保证调用 proxy 调用方法时一定能在 invoke 中调用对应的方法,同时,修改 Foo 接口的中的 bar() 方法,使其具有 int 类型的返回值,因此 InvocationHandlerinvoke() 方法也得有返回值,同时将代理对象本身作为第一个参数,具体代码如下:
public class A12 {

    interface Foo {
        void foo();

        int bar(int a);
    }

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

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

//    interface InvocationHandler {
//        Object invoke(Object proxy, Method method, Object... args) throws InvocationTargetException, IllegalAccessException;
//    }

    static class $Proxy0 extends Proxy implements Foo {

        private static Method foo;

        private static Method bar;

        static {
            try {
                foo = Class.forName("com.example.spring.a12.A12$Foo").getMethod("foo", new Class[0]);
                bar = Class.forName("com.example.spring.a12.A12$Foo").getMethod("bar", Integer.TYPE);
            } catch (NoSuchMethodException | ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }

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

        @Override
        public void foo() {

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

        @Override
        public int bar(int a) {
            try {
                return (Integer) h.invoke(this, bar, new Object[]{a});
            } catch (InvocationTargetException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            } catch (Throwable e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void main(String[] args) {
        Target target = new Target();

        Foo proxy = new $Proxy0(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object... args) throws InvocationTargetException, IllegalAccessException {
                System.out.println("before ...");
                Object invoke = method.invoke(target, args);
                System.out.println("after ...");
                return invoke;
            }
        });
        System.out.println(proxy.getClass().getName())
        proxy.foo();
        System.out.println(proxy.bar(123));

    }
}
  1. JDK动态代理的字节码是通过 java.lang.reflect.Proxy 类在运行时生成代理类的字节码,底层主要基于 java 内置的 sun.misc.ProxyGenerator 类和ASM技术实现的,那具体的代理类是什么样子呢?我们通过阿里的工具 arthas 进行反编译,可以进入 arthas 官网进行下载 arthas-boot.jar

为了方便 arthas 访问到引用,我们在 main 方法最后加上代码 System.in.read();,让程序处于等待

System.out.println(proxy.getClass().getName());
System.in.read();
启动 arthas
java -jar arthas-boot.jar

选择应用 java 进程

[INFO] JAVA_HOME: D:\develop\Java\jdk-21
[INFO] arthas-boot version: 4.0.5
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 23744 
  [2]: 28640 org.jetbrains.jps.cmdline.Launcher
  [3]: 25704 com.example.spring.a12.A12
  [4]: 28616 org.jetbrains.idea.maven.server.RemoteMavenServer36
反编译代理类

我们启动的应用是 A12,控制台输入序号 3,进去应用,使用 jad 命令对代理类反编译

[INFO] Attach process 25704 success.
[INFO] arthas-client connect 127.0.0.1 3658
  ,---.  ,------. ,--------.,--.  ,--.  ,---.   ,---.                           
 /  O  \ |  .--. ''--.  .--'|  '--'  | /  O  \ '   .-' 
|  .-.  ||  '--'.'   |  |   |  .--.  ||  .-.  |`.  `-.                          
|  | |  ||  |\  \    |  |   |  |  |  ||  | |  |.-'    |
`--' `--'`--' '--'   `--'   `--'  `--'`--' `--'`-----' 

wiki        https://arthas.aliyun.com/doc
tutorials   https://arthas.aliyun.com/doc/arthas-tutorials.html
version     4.0.5
main_class  com.example.spring.a12.A12
pid         25704
start_time  2025-05-26 23:37:57.660
currnt_time 2025-05-26 23:38:06.082                                             

[arthas@25704]$ jad com.example.spring.a12.$Proxy0

以下就是代理类反编译出的 java 代码

ClassLoader:                                                                                                                                                                                                                                                                        
+-sun.misc.Launcher$AppClassLoader@18b4aac2
  +-sun.misc.Launcher$ExtClassLoader@3acf6f83

Location:                                                                                                                                                                                                                                                                          

/*
 * Decompiled with CFR.
 *
 * Could not load the following classes:
 *  com.example.spring.a12.A12$Foo
 */
package com.example.spring.a12;

import com.example.spring.a12.A12;
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 A12.Foo {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    private static Method m0;

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

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.example.spring.a12.A12$Foo").getMethod("bar", Integer.TYPE);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m4 = Class.forName("com.example.spring.a12.A12$Foo").getMethod("foo", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            return;
        }
        catch (NoSuchMethodException noSuchMethodException) {
            throw new NoSuchMethodError(noSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException classNotFoundException) {
            throw new NoClassDefFoundError(classNotFoundException.getMessage());
        }
    }

    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 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 int bar(int n) {
        try {
            return (Integer)this.h.invoke(this, m3, new Object[]{n});
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

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

Affect(row-cnt:1) cost in 415 ms.
ASM

我们来模拟一下通过 ASM 操作字节码生成代理类

安装 ASM 插件

在这里插入图片描述

生成 ASM 代码

arthas 反编译的代理类的代码复制一份并创建 $Proxy0 类的 java 文件,右击选择 ASM插件生成字节码代码,然后将右侧生成的字节码代码复制一份,创建 $Proxy0Dump 类的 java 文件,然后 $Proxy0 就可以删掉了
在这里插入图片描述

运行 ASM 代码

生成代理类 **class **

public class TestProxyAsm {
    public static void main(String[] args) throws Exception {
        byte[] dump = $Proxy0Dump.dump();
        FileOutputStream outputStream = new FileOutputStream("$Proxy0.class");
        outputStream.write(dump);
        outputStream.close();
    }
}

在这里插入图片描述
可以看到生成的代理类 class,与我们反编译的代码都一致的
我们可以直接将二进制字节码加载后,就可以直接调用了

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

        ClassLoader loader = new ClassLoader() {
            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                return super.defineClass(name, dump, 0, dump.length);
            }
        };
        Class<?> proxyClass = loader.loadClass("com.example.spring.a12.$Proxy0");
        Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);

        A12.Target target = new A12.Target();
        A12.Foo proxy = (A12.Foo) constructor.newInstance(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("before ...");
                Object invoke = method.invoke(target, args);
                System.out.println("after ...");
                return invoke;
            }
        });
        proxy.foo();
        System.out.println(proxy.bar(1));
    }
}

JDK 的生成代理类方式肯定不像我们这样把代理类复制出来生成 ASM 代码,内部是需要获取接口的方法信息的,具体看 sun.misc.ProxyGenerator#generateProxyClass() 方法,Proxy.newProxyInstance()创建代理类实例最后就是调用这个方法生成二进制字节码的

在这里插入图片描述

反射优化

前 16 次是调用了java本地的MethodAccessor 的实现类,反射性能较低
第 17 次调用会生成代理类,优化为非反射调用(用 arthas 的 jad 工具反编译第 17 次调用生成的代理类)

public class TestMethodInvoke {
    public static void main(String[] args) throws Exception {
        Method foo = TestMethodInvoke.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;
        }
        Field delegate = Class.forName("jdk.internal.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");
    }
}

在这里插入图片描述
在这里插入图片描述

1.3.2 CGLIB代理

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxyDemo {

    static class Target{
        public void foo() {
            System.out.println("target foo");
        }
    }
	// 代理是子类型, 目标是父类型
    // 如果目标对象为final,或者代理类执行的方法为final,则无法进行代理
    // 而jdk代理都实现了接口,代理类和目标类是平级的关系,所以可以进行代理
    public static void main(String[] args) {
        Target target = new Target();
        //MethodInterceptor()决定了代理类中方法执行的行为
        Target proxy = (Target) Enhancer.create(Target.class, new MethodInterceptor() {
	         //四个参数分别是:
           //1、代理对象本身
            //2、当前代理类中执行的方法
            //3、方法执行时的参数
            //4、方法对象(可以避免反射调用方法)
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("before ...");

//                Object result= method.invoke(target, args);
//                Object result= methodProxy.invoke(target, args);//内部没有用到反射,需要目标
                Object result = methodProxy.invokeSuper(p, args);//内部没有用到反射,需要代理
                System.out.println("after");
                return result;
            }
        });

        proxy.foo();
    }
}

总结

  • 代理类不需要实现接口
  • 代理对象和目标对象是父子关系,代理类继承于目标类;
  • 目标类定义的时候不能加final修饰,否则代理类就无法继承目标类了,会报IllegalArgumentException
  • 目标类方法定义的时候不能加final修饰,否则代理类继承目标类以后就不能重写目标类的方法了
原理
模拟实现 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", Integer.TYPE);
            save2 = Target.class.getMethod("save", Long.TYPE);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void save() {
        try {
			// 参数1:代理类对象
			// 参数2:当前执行的方法
			// 参数3:实参数组
			// 参数4:方法代理(可以避免反射调用)
            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 class A13 {
    public static void main(String[] args) {
        Proxy proxy = new Proxy();

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

        proxy.save();
        proxy.save(1);
        proxy.save(2L);
    }
}
MethodProxy

使用 MethodProxy 可以不通过反射调用,提高性能
先创建每个方法的 MethodProxy 对象

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", Integer.TYPE);
            save2 = Target.class.getMethod("save", Long.TYPE);

            // 创建方法代理对象
            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 RuntimeException(e);
        }
    }

    // >>>>>>>>> 带原始功能的方法

    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);
        }
    }
}
public class A13 {
    public static void main(String[] args) {
        Proxy proxy = new Proxy();

        Target target = new Target();
        proxy.setMethodInterceptor(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("before");
//                Object invoke = method.invoke(target, objects); // 反射方式调用
                Object invoke = methodProxy.invoke(target, objects);// 内部无反射,结合目标用, spring 是使用这种方式
//                Object invoke = methodProxy.invokeSuper(o, objects); // 内部无反射,结合代理类使用
                return invoke;
            }
        });

        proxy.save();
        proxy.save(1);
        proxy.save(2L);
    }
}
CGLIB避免反射调用

MethodProxy 调用 invokeinvokeSuper 不会使用反射,因为调用时内部会生成两个辅助类(TargetFastClassProxyFastClass),这两个类都继承了 FastClass 类,内部重写了 FastClass 一些方法,这里我们自己实现这两个类,来说明 CGLIB 避免反射的原理
在这里插入图片描述

调用 invoke

在调用 MehodProxycreate 方法会创建两个方法的 Signature 和目标类、代理类的信息,在调用 invoke 内部使用了双重检查锁(Double-Checked Locking)来保证线程安全只创建一次 TargetFastClassTargetFastClass 会根据获取方法的编号来调用目标类的方法

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

    /**
     * 调用目标方法的编号
     * 在创建 MethodProxy 时,就会把编号固定好,假设
     *      save()      编号是 0
     *      save(int)   编号是 1
     *      save(long)  编号是 2
     *
     * @param signature  包括方法名字,参数、返回值
     * @return
     */

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

    /**
     *  根据方法编号,正常调用目标对象方法
     *
     * @param index 方法编号
     * @param target  目标对象
     * @param args 方法参数
     * @return
     */
    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;
        }
        throw new RuntimeException("无此方法");
    }
}
调用 invokeSuper

与调用 invoke 时基本相同,在调用 invokeSuper 会生成 ProxyFastClass,唯一不同的是:获取代理类中的方法编号时,应该获取带原始功能的方法,在 ProxyFastClass 调用的也是原始功能的方法,其本质就是在代理类中直接调用目标类的方法
在这里插入图片描述

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

    /**
     * 调用目标方法的编号
     * 在创建 MethodProxy 时,就会把编号固定好,假设
     *      save()      编号是 0
     *      save(int)   编号是 1
     *      save(long)  编号是 2
     *
     * @param signature  包括方法名字,参数、返回值
     * @return
     */

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

    /**
     *  根据方法编号,正常调用目标对象方法
     *
     * @param index 方法编号
     * @param target  目标对象
     * @param args 方法参数
     * @return
     */
    public Object invoke(int index, Object target, Object[] args) {
        if (index == 3) {
            ((Target) target).save();
            return null;
        } else if (index == 4) {
            ((Target) target).save((int) args[0]);
            return null;
        } else if (index == 5) {
            ((Target) target).save((long) args[0]);
            return null;
        }
        throw new RuntimeException("无此方法");
    }

    public static void main(String[] args) {
        ProxyFastClass proxyFastClass = new ProxyFastClass();
        int index = proxyFastClass.getIndex(new Signature("saveSuper", "()V"));
        proxyFastClass.invoke(index, new Target(), new Object[0]);
    }
}
分析 CGLIB 源码
public class CglibProxyDemo {

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

                Object result= methodProxy.invoke(target, args);//内部没有用到反射,需要目标,Spring 用的时这种
//                Object result = methodProxy.invokeSuper(o, args);//内部没有用到反射,需要代理
                return result;
            }
        });

        proxy.save();
    }
}

我们先看一下 MethodProxy 调用 invokeinvokeSuper,的源码

在这里插入图片描述
在这里插入图片描述
可以看到先调用了初始化方法 init 我们来看一下源码
在这里插入图片描述
可以看到使用了双重检查锁(Double-Checked Locking),调用 helper 创建了对象, 又调用 getIndex(),这不就是我们说的方法对应的编号吗?
在这里插入图片描述
FastClassInfo 是什么?
在这里插入图片描述
这不就是我们上面所说的两个 FastClassf1TargetFastClassf2ProxyFastClassi1 是目标类方法的编号,i2 是代理类的原始方法的编号

我们之前手动模拟了 ProxyTargetFastClassProxyFastClass,那 CGLIB 生成的字节码到底是什么样子的呢?之前我们是使用 arthas 来反编译,但是有时反编译的代码格式会又错误或者代码有缺失,所以这里我来使用其他手段获取到生成的二进制字节码
首先需要知道这三个类对应的类名,可以更好的定位代码,修改一下代码

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

                Object result= methodProxy.invoke(target, args);//内部没有用到反射,需要目标,Spring 用的时这种
//                Object result = methodProxy.invokeSuper(o, args);//内部没有用到反射,需要代理

                System.out.println("代理类:" + o.getClass().getName());
                Field fastClassInfo = methodProxy.getClass().getDeclaredField("fastClassInfo");
                fastClassInfo.setAccessible(true);
                Object fastClassInfoObj = fastClassInfo.get(methodProxy);

                Field f1 = fastClassInfoObj.getClass().getDeclaredField("f1");
                f1.setAccessible(true);
                System.out.println("TargetFastClass:" + f1.get(fastClassInfoObj).getClass().getName());

                Field f2 = fastClassInfoObj.getClass().getDeclaredField("f2");
                f2.setAccessible(true);
                System.out.println("ProxyFastClass:" + f2.get(fastClassInfoObj).getClass().getName());


                return result;
            }
        });

        proxy.save();
    }

在这里插入图片描述
Enhancer.create() 方法需要生成类的字节码,最终会调用到 org.springframework.cglib.core.AbstractClassGenerator#generate 方法,图中箭头指向的 byte 数组就是字节码二进制数据,那我们拿到二进制数据,生成 class 文件就能看到类的信息了
在这里插入图片描述
这里我对代码 debug,将打印出来的三个类名作为匹配条件,可以更精确的定位代码

// 匹配条件
className.equals("com.example.spring.a13.Target$$EnhancerByCGLIB$$6580fbf7") || className.equals("com.example.spring.a13.Target$$FastClassByCGLIB$$20f4e08c") || className.equals("com.example.spring.a13.Target$$EnhancerByCGLIB$$6580fbf7$$FastClassByCGLIB$$ebf7de7e")

在这里插入图片描述

第一个生成的就是 com.example.spring.a13.Target$$EnhancerByCGLIB$$6580fbf7
在这里插入图片描述
我们通过 Idea Evaluate 工具可以将数组转换成字符串,然后拷贝出来
在这里插入图片描述
写一个生成 class 文件的代码

public static void main(String[] args) throws IOException {
		// 将拷贝出来的二进制数组放入
        byte[] b = new byte[]{-54, -2, -70, -66, 0, 0, 0, 52, 1, 5, 1, 0, 56, 99, 111, 109, ..........};
        // 文件名需要和类名一致
        FileOutputStream fileOutputStream = new FileOutputStream("Target$$EnhancerByCGLIB$$6580fbf7.class");
        fileOutputStream.write(b, 0, b.length);
        fileOutputStream.close();
    }

按照相同的方式生成了三个类的 class 文件
在这里插入图片描述
首先我们看代理类的代码
在这里插入图片描述

可以从图中看到,代理类创建了 MethodProxy 生成了原始方法调用目标方法,和我们通过模拟的代码一致

再看一下目标类的 FastClass
在这里插入图片描述
在这里插入图片描述
可以看到 getIndex() 通过 SignaturehashCode 进行编号,在判断方法名称返回编号,在调用 invoke 时也是通过编号进行匹配方法调用的,只不过我们是用的 if,这是用的 switch 而已
同样的,代理类生成的 FastClass 也是相同的实现方式了,因为代码比较多,就不贴图了,按照步骤自己生成 class 查看

代理方式对比

jdk 对比,jdk 是调用17次时,针对一个方法产生一个代理类
CGLIBMethodProxy 调用的时候就产生代理,一个 proxy 代理类对应两个 FastClass 的子类
(一个是 MethodProxy 调用 invoke 时结合目标使用,一个是 MethodProxy 调用 invokeSuper 时结合代理使用)
在这里插入图片描述

性能差异总结
  • 创建代理对象速度:JDK 动态代理更快,CGLIB 需要生成字节码较慢。
  • 方法调用速度:CGLIB 在大多数情况下更快,特别是在高频调用场景。
  • 内存占用:CGLIB 生成的代理类会占用更多永久代内存(在 Java 8 之前)。
  • 适用场景:JDK 代理适合接口代理,CGLIB 适合类代理且对性能要求高的场景。

来创建方法的编号,和目标类代理类对应的 FastClass
在这里插入图片描述

在这里插入图片描述
init() 方法可以看到,调用了 helper 创建了 FastClassf1 就是目标类的 FastClassf2 就是代理类的 FastClass,这也就对应上了 invokeinvokeSuper 代码了

2. Spring 选择代理

  • 切点:增强的匹配规则
  • 通知:增强的逻辑
  • 切面:切点和通知组合

两个切面的概念

  • aspect =
    ​ 通知1(advice)+ 切点1(pointcut)
    ​ 通知2(advice)+ 切点2(pointcut)
    ​ 通知3(advice)+ 切点3(pointcut)
  • advisor = 更细粒度的切面,只包含一个通知和切点

aspect 生效之前,会被拆解成多个 advisor

2.1 接口及目标类实现

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

2.2 准备切点

AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* foo())");

2.3 准备通知

注:要使用 org.aopalliance.intercept.MethodInterceptor

MethodInterceptor advice = new MethodInterceptor() {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("before...");
        Object res = invocation.proceed();//调用目标
        System.out.println("after...");
        return res;
    }
};

2.4 准备切面

DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut,advice);

2.5 创建代理

ProxyFactory factory = new ProxyFactory();
factory.setTarget(new Target1()); // 添加目标
factory.addAdvisor(advisor); // 添加切面
I1 proxy = (I1) factory.getProxy();
System.out.println(proxy.getClass());

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

2.6 选择代理的方式

  • proxyTargetClass = false,目标类实现了接口,用 JDK 代理
  • proxyTargetClass = false,目标类没有实现了接口,用 CGLIB 代理
  • proxyTargetClass = true,用 CGLIB 代理

选择1

proxyTargetClass = false,而且目标实现了接口, 使用 JdkDynamicAopProxy 实现(默认proxyTargetClass = false

Target1 target1 = new Target1();
ProxyFactory factory = new ProxyFactory();
factory.setTarget(target1); // 添加目标
factory.addAdvisor(advisor); // 添加切面

factory.setProxyTargetClass(false);
factory.setInterfaces(target1.getClass().getInterfaces()); // 添加目标类实现的接口

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

选择2

proxyTargetClass = false,而且目标没有接口, 使用 ObjenesisCglibAopProxy 实现

Target1 target1 = new Target1();
ProxyFactory factory = new ProxyFactory();
factory.setTarget(target1); // 添加目标
factory.addAdvisor(advisor); // 添加切面

factory.setProxyTargetClass(false);

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

选择3

proxyTargetClass = true总是使用 ObjenesisCglibAopProxy 实现

Target1 target1 = new Target1();
ProxyFactory factory = new ProxyFactory();
factory.setTarget(target1); // 添加目标
factory.addAdvisor(advisor); // 添加切面

factory.setProxyTargetClass(true);

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

3. 切点匹配

    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() {
        }
    }
  1. 创建 AspectJExpressionPointcut 对象,然后调用 setExpression 方法,在其中填写切点表达式
  2. 调用 matches 方法判断是否与切点表达式匹配,第一个参数为对应类的方法名,第二个参数为对应类的 class
public static void main(String[] args) throws Exception {
    AspectJExpressionPointcut pointcut1 = new AspectJExpressionPointcut();
    pointcut1.setExpression("execution(* bar())");
    System.out.println(pointcut1.matches(T1.class.getMethod("foo"), T1.class));
    System.out.println(pointcut1.matches(T1.class.getMethod("bar"), T1.class));

    AspectJExpressionPointcut pointcut2 = new AspectJExpressionPointcut();
    pointcut2.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)");
    System.out.println(pointcut2.matches(T1.class.getMethod("foo"), T1.class));
    System.out.println(pointcut2.matches(T1.class.getMethod("bar"), T1.class));
}
  1. 为了保证当出现T3类的情况时(实现了被注解标注的接口)@Transactional注解能够被匹配到,在 Ppring 内部并不是采用以上方法匹配 @Transactional 注解,而是创建 StaticMethodMatcherPointcut 对象并重写 matches 方法,在方法内部添加匹配注解的逻辑
    spring探秘之组合注解的处理(点我)
StaticMethodMatcherPointcut pointcut3 = 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(pointcut3.matches(T1.class.getMethod("foo"), T1.class));	//true
System.out.println(pointcut3.matches(T1.class.getMethod("bar"), T1.class));	//false
System.out.println(pointcut3.matches(T2.class.getMethod("foo"), T2.class));	//true
System.out.println(pointcut3.matches(T3.class.getMethod("foo"), T3.class));	//true

检查策略

  • DIRECT:只查找元素上直接声明的注解,不包括通过@Inherited 继承的注解。
  • INHERITED_ANNOTATIONS:只查找元素直接声明或通过@Inherited 继承的注解;
  • SUPERCLASS:查找元素直接声明或所有父类的注解;
  • TYPE_HIERARCHY:查找元素、所有父类以及实现的父接口的全部注解;
  • TYPE_HIERARCHY_AND_ENCLOSING_CLASSES:查找封闭类以及其子类

4. @Aspect与Advisor

 public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("aspect1", Aspect1.class);
        context.registerBean("config", Config.class);
        context.registerBean(ConfigurationClassPostProcessor.class);
        context.refresh();
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }

    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(MethodInterceptor advice3) {
            //定义切点
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
            pointcut.setExpression("execution(* foo())");
            DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice3);
            return advisor;
        }

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

4.1 AnnotationAwareAspectJAutoProxyCreator 作用

自动代理后处理器 AnnotationAwareAspectJAutoProxyCreator会帮我们创建代理

通常代理创建会在原始对象初始化后执行, 但碰到循环依赖会提前至依赖注入之前执行

高级的 @Aspect 切面会转换为低级的 Advisor 切面

为了方便调用 protected 方法,新写了一个 AnnotationAwareAspectJAutoProxyCreator 继承

static class AnnotationAwareAspectJAutoProxyCreator extends org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator {

        @Override
        protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
            return super.findEligibleAdvisors(beanClass, beanName);
        }

        @Override
        protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
            return super.wrapIfNecessary(bean, beanName, cacheKey);
        }
    }

4.1.1 findEligibleAdvisors方法

解析某个类的切面,一部分切面是低级的,如准备代码中的 advisor3 ;另一部分是高级的, 由解析 @Aspect 后获得。解析时会将高级切面解析成多个低级切面

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
		List<Advisor> candidateAdvisors = findCandidateAdvisors();
		List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
		extendAdvisors(eligibleAdvisors);
		if (!eligibleAdvisors.isEmpty()) {
			eligibleAdvisors = sortAdvisors(eligibleAdvisors);
		}
		return eligibleAdvisors;
	}
AnnotationAwareAspectJAutoProxyCreator proxyCreator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
        List<Advisor> advisorList = proxyCreator.findEligibleAdvisors(Target1.class, "target1");
        for (Advisor advisor : advisorList) {
            System.out.println(advisor);
        }

在这里插入图片描述

4.1.2 wrapIfNecessary方法

判断是否有必要对目标进行代理,内部调用 findEligibleAdvisors, 只要返回集合不空, 则表示需要创建代理

如下面代码,因为在准备时 Target2 中的 bar 方法,没有与切点进行匹配,所以 Target2 在调用findEligibleAdvisors方法后获取的切面集合为空,所以没必要进行代理

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

在这里插入图片描述

4.2 代理创建时机

Bean创建的三个重要阶段分别为:创建 -> (?) 依赖注入 -> 初始化 (?)
代理创建的时机有两个位置,一个是创建Bean和依赖注入之间,另一个是初始化之后

4.2.1 初始化之后

下面代码中因为 Bean1 中有 foo 方法,所以 Bean1 将会被代理,而且在Bean2 中还注入了 Bean1

在这种单向的依赖关系下(Bean2依赖Bean1),代理在Bean1的初始化之后创建,在代理创建之后,才继续执行Bean2的构造方法,在Bean2中注入Bean1的代理,最后完成Bean2的初始化

	@Configuration
    static class Config {
        @Bean
        public AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator() {
            return new AnnotationAwareAspectJAutoProxyCreator();
        }

        @Bean
        public AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor() {
            return new AutowiredAnnotationBeanPostProcessor();
        }

        @Bean
        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 (MethodInvocation invocation) -> {
                System.out.println("before...");
                return invocation.proceed();
            };
        }

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

在这里插入图片描述

4.3 创建Bean和依赖注入之间

下面代码中因为Bean1中有foo方法,所以Bean1将会被代理,而且在Bean1中注入了Bean2,在Bean2中注入了Bean1。在这种循环依赖的关系下,代理在Bean1的构造和Bean1的依赖注入之间创建的。

原因:因为在Bean1中需要注入Bean2,所以执行完Bean1的构造后,就执行Bean2的构造,因为Bean2中需要注入Bean1的代理,所以再创建Bean1的代理。

static class Bean1 {
        public void foo() {
        }

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

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

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

在这里插入图片描述

4.4 切面顺序控制

使用 @Order 注解进行控制,默认是最低优先级,值越小优先级越高
注意:可以加在高级切面类上,但对于低级切面类,需要在DefaultPointcutAdvisor类中setOrder,@Order 加在 @Bean 的方法上没有用,只能加载类上

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

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

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

4.5 高级切面转为低级切面(@Before为例)

通知类型如下

  • AspectJMethodBeforeAdvice (前置通知)
  • AspectJAroundAdvice (环绕通知)
  • AspectJAfterReturningAdvice (后置通知:正常返回)
  • AspectJAfterThrowingAdvice(后置通知:异常)
  • AspectJAfterAdvice (后置通知)
public class A17_2 {

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

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

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

        public Object around(ProceedingJoinPoint pjp) throws Throwable {
            System.out.println("around start");
            Object proceed = pjp.proceed();
            System.out.println("around start");
            return proceed;
        }
    }

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

    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()) {
            //判断方法上是否有加@Before注解
            if (method.isAnnotationPresent(Before.class)) {
                // 根据@Before注解的value值来生成一个切点对象
                String expression = method.getAnnotation(Before.class).value();//切点表达式
                AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();//解析后生成的切点对象
                pointcut.setExpression(expression);
                // 通知类
                AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);
                // 低级切面
                Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
                list.add(advisor);
            }
        }
        for (Advisor advisor : list) { System.out.println(advisor); }
    }
}

5. 静态通知调用

5.1 不同通知统一转换成环绕通知

ProxyFactory 在创建代理时,最后调用 advice 的是一个 MethodInvocation 对象(调用链对象)
1、因为 advisor 有多个, 且一个套一个调用, 因此需要一个调用链对象
2、MethodInvocation调用次序如下
请添加图片描述
3、为了实现上图所示的调用次序,环绕通知最适合,因此其他 before、afterReturning 都会被转换成环绕通知
4、统一转换为环绕通知, 体现的是设计模式中的适配器模式(点击查看设计模式)

统一转换为 MethodInterceptor 环绕通知, 这体现在方法中的 Interceptors 上

  • MethodBeforeAdviceAdapter 将 @Before AspectJMethodBeforeAdvice 适配为 MethodBeforeAdviceInterceptor
  • AfterReturningAdviceAdapter 将 @AfterReturning AspectJAfterReturningAdvice 适配为 AfterReturningAdviceInterceptor
  • 对外是为了方便使用要区分 before、afterReturning
  • 对内统一都是环绕通知, 统一用 MethodInterceptor 表示

定义一个高级切面,里面含有多个通知类型

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

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

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

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

        @Around("execution(* foo())")
        public Object around(ProceedingJoinPoint pjp) throws Throwable {
            System.out.println("around start");
            Object proceed = pjp.proceed();
            System.out.println("around end");
            return proceed;
        }
    }

将不同的通知类型统一转换成环绕通知

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()) {
            //判断方法上是否有加@Before注解
            if (method.isAnnotationPresent(Before.class)) {
                // 根据@Before注解的value值来生成一个切点对象
                String expression = method.getAnnotation(Before.class).value();//切点表达式
                AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();//解析后生成的切点对象
                pointcut.setExpression(expression);
                // 通知类
                AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);
                // 低级切面
                Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
                list.add(advisor);
            } else if (method.isAnnotationPresent(After.class)) {

                String expression = method.getAnnotation(After.class).value();//切点表达式
                AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();//解析后生成的切点对象
                pointcut.setExpression(expression);
                // 通知类
                AspectJAfterAdvice advice = new AspectJAfterAdvice(method, pointcut, factory);
                // 低级切面
                Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
                list.add(advisor);
            } else if (method.isAnnotationPresent(AfterReturning.class)) {

                String expression = method.getAnnotation(AfterReturning.class).value();//切点表达式
                AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();//解析后生成的切点对象
                pointcut.setExpression(expression);
                // 通知类
                AspectJAfterReturningAdvice advice = new AspectJAfterReturningAdvice(method, pointcut, factory);
                // 低级切面
                Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
                list.add(advisor);
            } else if (method.isAnnotationPresent(AfterThrowing.class)) {

                String expression = method.getAnnotation(AfterThrowing.class).value();//切点表达式
                AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();//解析后生成的切点对象
                pointcut.setExpression(expression);
                // 通知类
                AspectJAfterThrowingAdvice advice = new AspectJAfterThrowingAdvice(method, pointcut, factory);
                // 低级切面
                Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
                list.add(advisor);
            } else if (method.isAnnotationPresent(Around.class)) {

                String expression = method.getAnnotation(Around.class).value();//切点表达式
                AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();//解析后生成的切点对象
                pointcut.setExpression(expression);
                // 通知类
                AspectJAroundAdvice advice = new AspectJAroundAdvice(method, pointcut, factory);
                // 低级切面
                Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
                list.add(advisor);
            }
        }
        for (Advisor advisor : list) { System.out.println(advisor); }
 		

		Target target = new Target();
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(target);
        proxyFactory.addAdvice(ExposeInvocationInterceptor.INSTANCE); // 准备把 MethodInvocation 放入当前线程
        proxyFactory.addAdvisors(list);


        List<Object> methodInterceptorList = proxyFactory.getInterceptorsAndDynamicInterceptionAdvice(Target.class.getMethod("foo"), Target.class);
        for (Object o : methodInterceptorList) {
            System.out.println(o.getClass());
        }
   }

5.1.1 调用链执行过程

注意:在调用链执行的过程中,某些通知内部有可能会使用到调用链对象。所以必须使用一个最外层的环绕通知,将 MethodInvocation 放入当前线程

//创建并执行调用链 (多个环绕通知 + 目标)
MethodInvocation methodInvocation = new ReflectiveMethodInvocation(
        /*
         *  参数一:代理对象
         *  参数二:目标对象
         *  参数三:目标中对应的方法
         *  参数四:目标方法的实参数组
         *  参数五:目标类型
         *  参数六:环绕通知集合
         */
        null, target, Target.class.getMethod("foo"), new Object[0], Target.class, methodInterceptorList
);
//将环绕通知一层层逐一进行调用
methodInvocation.proceed();

在这里插入图片描述

5.1.2 模拟实现调用链

在proceed方法内部首先需要将可调用次数count(初始化为1)与通知的list集合比较,如果可调用次数大,说明集合为空,没有通知,则直接调用目标。如果集合不为空,那么就递归调用下一个通知

注意:此处递归调用并不是直接在proceed方法内部调用proceed而是通过interceptor调用invoke,从而间接地在切面中调用proceed方法

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

static class Advice1 implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("Advice1 before...");
        Object res = invocation.proceed();	//调用下一个通知或目标
        System.out.println("Advice1 after...");
        return res;
    }
}

static class Advice2 implements MethodInterceptor {
    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 MyInvocation implements MethodInvocation {
    private Object target;
    private Method method;
    private Object[] args;
    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 Object proceed() throws Throwable {
        if (count > methodInterceptorList.size()){
            //调用目标,返回并结束递归
            return method.invoke(target, args);
        }
        //调用通知,count+1
        MethodInterceptor interceptor = methodInterceptorList.get(count++ - 1);
        return interceptor.invoke(this);
    }

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

    @Override
    public Object[] getArguments() {return args;}
   
    @Override
    public Object getThis() {return target;}
    
    @Override
    public AccessibleObject getStaticPart() {return method;}
}

测试

public static void main(String[] args) throws Throwable {
    Target target = new Target();
    List<MethodInterceptor> list = List.of(
            new Advice1(),
            new Advice2()
    );
    MyInvocation invocation = new MyInvocation(target, Target.class.getMethod("foo"), new Object[0], list);
    invocation.proceed();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值