spring framework aop
👍 aop 是对 oop 的一种补充。增强功能
2022/5/26 10:25:37
➡️

aop 代理

aop 是对 oop 的一种补充。

代理的好处:

  • 控制本来的实现类的可访问性,透过代理对象访问,可以在代理类内创建原本的实现类,对用户而言减少创建的复杂性
  • 做横向拦截,给实现类在不修改代码的情况下,增强功能
    1. 业务日志切面:可以记录业务方法调用的痕迹
    2. 事务控制:通过切面可以声明式控制事务
    3. 权限校验:执行方法之前先校验当前登录用户是否有权调用
    4. 数据缓存:执行方法之前先从缓存中取,取到则直接返回不走业务方法

静态代理

普通的静态代理需要给每个被代理的类创建一个代理类

public class UserStaticProxy implements UserService{
    private UserService userService;
    public UserStaticProxy(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void addUser() {
        userService.addUser();
        System.out.println("增强的功能");
    }
}

动态代理

动态代理是指代理关系在运行时确定的代理模式,因为代理类在编译前不存在,代理关系到运行时才能确定,因此称为动态代理。 动态代理,可以解决静态代理导致的代理类膨胀的问题

jdk 动态代理

核心 API 是 Proxy 类和 InvocationHandler 接口。它的原理是利用反射机制在运行时生成代理类的字节码,代理类会实现接口 和继承 Proxy 类

使用步骤:

  1. 定义业务接口
  2. 被代理对象实现业务接口
  3. 通过 Proxy 的静态方法 newProxyInstance( ClassLoader loader, Class[] interfaces, InvocationHandler h) 创建一个代理对象
  4. 使用代理对象

优点:

  • 无外部依赖,不用担心java 版本升级

缺点

  • 被代理的类必须要有接口,因为被代理的类也要实现接口,也就是只能代理接口中的方法
  • InvocationHandler 功能增强实现类里要传入被代理类的对象实例
  • 只能代理方法

示例代码

定义的俩个接口

package com.hank.uti;

public interface ServiceInterface1 {
     void doSome1();
     void doSome2();
}

public interface ServiceInterface2 {
     void doSome3();
     void doSome4();
}


增强的功能,需要做横向拦截进行功能增强,需要实现 InvocationHandler


package com.hank.uti;
//上面接口处的实现类,此类要隐藏可访问性
//此类只能在包内 com.hank.uti 访问,需要限制不能在包外部被调用
class Service implements ServiceInterface1, ServiceInterface2 {
  @Override
  public void doSome1() {
    System.out.println("doSome1");
  }

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

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

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

//对 Service 类的方法进行功能增强,做横向拦截的,最为麻烦的是这里要传入一个被代理对象的实例 new Service(),
//会影响通用性
//默认是给所有的接口里的方法进行了代理,如果某些方法不需要进行功能增强,可以根据进行过滤
public class ServiceEnhance implements InvocationHandler {
 
  //被代理的实例对象
  private final Service service;

  public ServiceEnhance() {
    //如果被代理的很复杂,可以在这里构造,对外部用户用好
    this.service=new Service();
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     System.out.println("代理的方法 "+ method.getName()+" 开始执行");

     //被代理的类的方法执行,最为麻烦的是这里要传入一个被代理对象的实例
     var ret= method.invoke(service,args);

     System.out.println("代理的方法 "+ method.getName()+" 执行结束");
     return ret;
  }
}
package com.hank;

public class Programmer {
  public static void main(String[] args) {
   
    //代理要增强的方法
    var handler=new ServiceEnhance();


    // jdk 动态代理反射生成的代理对象
    // jsk 动态代理是基于接口的,第二个参数要传递实现了需要实现的接口数组
    // 第三个参数是对那个对象进行增强的 InvocationHandler 实现对象
    var obj=Proxy.newProxyInstance(ServiceInterface1.class.getClassLoader(),new Class[]{
        ServiceInterface1.class,ServiceInterface2.class,
    }, handler);

    //或者通过如下方式创建
    var clazz =Proxy.getProxyClass(ServiceInterface1.class.getClassLoader(),new Class[]{
        ServiceInterface1.class,ServiceInterface2.class});
    //创建的代理对象
    var obj1= clazz.getConstructor(InvocationHandler.class).newInstance(handler);

    //Proxy 静态方法
    System.out.println("是否是代理类"+Proxy.isProxyClass(obj.getClass()));
    System.out.println("功能增强器的名字"+Proxy.getInvocationHandler(obj).getClass().getName());

    //代理的接口是 ServiceInterface1 & ServiceInterface2
    var serviceProxy =(ServiceInterface1 & ServiceInterface2)obj;
    serviceProxy.doSome1();
    serviceProxy.doSome2();
    serviceProxy.doSome3();
    serviceProxy.doSome4();
  }
}

CGLIB 动态代理

CGlib(Code Generation Library)是一个开源项目;是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口,它被AOP、测试、数据访问框架用于生成动态代理对象和拦截字段访问。

CGlib包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类;CGlib是针对类来实现代理的,原理是对指定的业务类生成一个子类(继承),如果有接口会实现接口,并覆盖其中业务方法实现代理;所以CGlib可以为无接口的类直接做代理,当然有接口的类也是可以的并无影响.

ASM是一种通用Java字节码操作和分析框架。它可以用于修改现有的class文件或动态生成class文件,需要懂 java 底层的字节码及类文件格式功能才能使用,门槛很高

优点:

  • 不需要被代理的类必须实现接口,因为代理类是继承被代理类实现的
  • MethodInterceptor 功能增强实现类里不需要传入被代理类的对象的实例

缺点:

  • 需要引入外部依赖
     <dependency>
       <groupId>cglib</groupId>
       <artifactId>cglib</artifactId>
       <version>3.3.0</version>
     </dependency>
    
  • 因为是对指定的类生成一个子类,覆盖其中的方法,并给其中方法的增强功能,但是因为采用的是继承,所以该类或方法最好不要生成final,对于final类或方法,是无法继承的
  • 只能代理方法

实例代码

class Service implements ServiceInterface1, ServiceInterface2 {
  
  //增加构造函数
  public Service(String str){
    System.out.println("被代理对象创建了");
  }
  //.......余下代码省略
}

// 这里和 jdk 动态代理不同的是,对象实例不需要提供,可以做到一定的通用性
//默认是给所有的接口里的方法进行了代理,如果某些方法不需要进行功能增强,可以根据进行过滤
public class ServiceMethodInterceptor implements MethodInterceptor {
  @Override
  public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) 
    throws Throwable {
      System.out.println("代理的方法 "+ method.getName()+" 开始执行");

      //对象实例在接口方法里包含 Object o
      var ret= methodProxy.invokeSuper(o,objects);

      System.out.println("代理的方法 "+ method.getName()+" 执行结束");
      return ret;
  }
}
/*
被代理对象创建了
代理的方法 doSome1 开始执行
doSome1
代理的方法 doSome1 执行结束
代理的方法 doSome2 开始执行
doSome2
代理的方法 doSome2 执行结束
代理的方法 doSome3 开始执行
doSome3
代理的方法 doSome3 执行结束
代理的方法 doSome4 开始执行
doSome4
代理的方法 doSome4 执行结束
*/
public class Programmer {
  public static void main(String[] args) throws ClassNotFoundException {
    {
      //Service 对象增强功能的对象
      var handler=new ServiceMethodInterceptor();
      
      var enhancer = new Enhancer();
      //设置给那个类增强,不能是接口这里,因为是通过继承父类的方式生成的代理类,说以 api 叫 setSuperclass
      //默认是给对象的所有方法进行了代理(除过静态 final)
      enhancer.setSuperclass(Class.forName("com.hank.uti.Service"));
      //设置回调对象,也就是增强的功能
      enhancer.setCallback(handler);
     
      //生成代理的对象
      //默认调用的是是被代理类的无参构造器,也可以指定构造器的参数
      //如下传入构造器需要的参数类型及值
      var proxy =(ServiceInterface1 & ServiceInterface2)enhancer.create(
          new Class[]{String.class},new Object[]{"para1"});
      proxy.doSome1();
      proxy.doSome2();
      proxy.doSome3();
      proxy.doSome4();
    }
  }
}

可以传递多个功能增强器,但是需要指定过滤器

不同于Proxy.newProxyInstance 的方法一次只能传递一个功能增强处理对象,CGLIB 可以传入多个,但是要提供过滤器,但是一个方法一次也只能有一个增强器

package com.hank.uti;

//在定义一个增强对象
public class ServiceMethodInterceptor1 implements MethodInterceptor {
  @Override
  public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) 
  throws Throwable {
    System.out.println("代理的方法 "+ method.getName()+" 开始执行1");
    var ret= methodProxy.invokeSuper(o,objects);
    System.out.println("代理的方法 "+ method.getName()+" 执行结束1");
    return ret;
  }
}

package com.hank;

/*
被代理对象创建了
代理的方法 doSome1 开始执行
doSome1
代理的方法 doSome1 执行结束
代理的方法 doSome2 开始执行
doSome2
代理的方法 doSome2 执行结束

代理的方法 doSome3 开始执行1
doSome3
代理的方法 doSome3 执行结束1
代理的方法 doSome4 开始执行1
doSome4
代理的方法 doSome4 执行结束1
*/
public class Programmer {
  public static void main(String[] args) throws Exception {
    {
      //Service 对象增强功能的对象
      var handler=new ServiceMethodInterceptor();
      var handler1=new ServiceMethodInterceptor1();
      var enhancer = new Enhancer();
      //设置给那个类增强,不能是接口这里,因为是通过继承父类的方式生成的代理类,说以 api 叫 setSuperclass
      enhancer.setSuperclass(Class.forName("com.hank.uti.Service"));

      //设置回调对象,也就是增强的功能,此处设置了多个增强器对象,但是一个被代理对象的方法一次只能被一
      //个增强器处理,所以还要定义增强器过滤器
      enhancer.setCallbacks(new Callback[]{handler,handler1});

      //增强器过滤器,指定那个方法被那个增强器处理
      enhancer.setCallbackFilter(new CallbackFilter() {
        @Override
        public int accept(Method method) {
          //这里返回的数字 0,1 代表的是 setCallbacks 传入的的回调数组里的回调对象在数组里的顺序
          if(method.getName().equalsIgnoreCase("doSome1") || 
             method.getName().equalsIgnoreCase("doSome2")) return 0;
          return 1;
        }
      });
      
      //生成代理的对象,默认调用的是是被代理类的无参构造器,也可以指定构造器的参数
      var proxy =(ServiceInterface1 & ServiceInterface2)enhancer.create(
                  new Class[]{String.class},new Object[]{"para1"});
      proxy.doSome1();
      proxy.doSome2();
      proxy.doSome3();
      proxy.doSome4();
    }
  }
}

Callback 增强器的其他功能

CGLIB中提供的Callback的子类有以下几种:

  • NoOp 不做增强
  • FixedValue 不会执行被代理的方法的内部逻辑,直接返回固定的值
  • InvocationHandler 用法和 jdk 动态代理的 InvocationHandler 一样
  • MethodInterceptor
  • Dispatcher 调用代理对象的方法,每次都要求生成一个新被代理对象
  • LazyLoader 延迟生成被代理对象,在执行代理对象的方法时才生成被代理对象,之后使用相同的代理对象

字节码技术的其他探索

CGLIB 因为采用的是 ASM 为底层的字节码生成技术能做的事情不仅仅是 aop 这些 比如如下的直接生成一个对象

ben 生成器

var beanGenerator = new BeanGenerator();
//增加属性
beanGenerator.addProperty("age",int.class);
//凭空生成对象,就像是变魔术一样
var obj =  beanGenerator.create();

//奇怪的不能直接获取到字段
System.out.println(Arrays.stream(obj.getClass().getFields()).map(p->{
         p.setAccessible(true);
         return p.getName();
       }).collect(Collectors.toList()).size());
//0

var setter=   obj.getClass().getMethod("setAge",int.class );
setter.invoke(obj,10);

var getter=   obj.getClass().getMethod("getAge");
System.out.println(getter.invoke(obj));
//10

Byte Buddy 类似于CGLIB 的字节码框架

Byte Buddy 的目标是代替 CGLIB

//TODO Byte Buddy

AspectJ 静态织入

AspectJ 是一个在编译器和运行时都支持代码增强(织入)的开源框架,曾经非常流行AspectJ,现在属于 eclipse 开源基金会组织. AspectJ 是最早、功能比较强大的 AOP 实现之一, 对整套 AOP 机制都有较好的实现,很多其他语言的 AOP 实现,也借鉴或采纳了 AspectJ 中很多设计。在 Java 领域,AspectJ 中的很多语法结构基本上已成为 AOP 领域的标准。 AspectJ可以做Spring AOP干不了的事情,它是AOP编程的完全解决方案,AspectJ支持所有切入点,不仅仅是方法织入,因为AspectJ在实际运行之前就完成了织入,所以说它生成的类 是没有额外运行时开销的,缺点是使用复杂,它不会生成代理类,是在运行代码前直接修改 class 文件字节码的方式织入增强功能到原文件,其内部使用的是 BCEL 框架 来完成字节码的修改能力。 aspectj 地址

aspectj 定义了一套专有名称的概念

  • Aspect切面:一个分布在应用程序中多个位置的标准代码/功能是对现有业务的功能增强,通常与实际的业务逻辑不同,每个切面都侧重于一个特定的横切功能.
  • Joinpoint连接点:这是程序执行中的特定点,如方法执行,构调用造函数或字段赋值等,也就是需要指定增强代码在业务代码的插入点.
  • Advice通知:在一个连接点中,切面增强的具体功能代码,有 Before,After,Around,After,AfterReturning ,AfterThrowing 通知
  • Pointcut切点:一个匹配连接点的正则表达式。 每当任何连接点匹配一个切入点时,就执行与该切入点相关联的指定增强通知.
  • Weaving织入:链接切面和目标对象来创建一个通知对象的过程.

因为是直接去改原来的 class文件,所以它可以在如下五个位置插入自定义的代码:

  1. 在方法(包括构造方法)被调用的位置。
  2. 在方法体(包括构造方法)的内部。
  3. 在读写变量的位置。
  4. 在静态代码块内部。
  5. 在异常处理的位置的前后。

使用前期条件:

  1. IDE 安装插件Aspect,这样在编写 .aj 文件的时候有智能提示及语法检查
  2. maven 安装依赖
    <!--处理 aspectj  注解-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>1.9.9.1</version>
    </dependency>
      <!--它的主要作用是负责解析切入点表达式,在加载是织入是会用到-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.9.1</version>
    </dependency>
    <!--ajc编译器,比如 .aj文件  需要它去编译,需要配合IDE 的插件 AspectJ 才能起到作用-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjtools</artifactId>
        <version>1.9.9.1</version>
    </dependency>
    

3.编译时织入代码到 class 文,需要靠此插件支持,如果不用插件要用命令调用

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.14.0</version>
    <configuration>
        <complianceLevel>11</complianceLevel>
        <source>11</source>
        <target>11</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>

静态织入

在编译的时或类加载到 JVM 的时候,直接修改原来的class文件内容,把增强的功能直接混入原本的 class 文件

被代理的类

package com.hank.aop;

public class Bar {
  public  String  foo(String name){
    return "foo 方法返回结果"+name;
  }
}

编译时织入.aj 的功能增强文件

利用ajc编译器替代 javac 编译器,直接将源文件 (java或者aspect文件) 编译成 class 文件并将切面织入进代码。

代理类,此类是在 IDE 安装插件后通过右键添加 Aspect 类型的文件,文件用 .aj 结尾 此文件的的语法要参考 aspect .aj 文件的格式

package com.hank.aop;

public aspect BarEnhance {
  private String show(JoinPoint point){
    return "目标类对象:"+point.getTarget ()+
           ",被增强的方法:"+point.getSignature ().getName ()+
           "传入的参数是"+String.join(";", Arrays.stream(point.getArgs()).
           map(p->p.toString()).collect(Collectors.toList()));
  }

  pointcut  pt1() : execution(* com.hank.aop.Bar.*(..));

  before() : pt1(){
    System.out.println("编译时织入 before 增强,"+show(thisJoinPoint));
  }

  Object around()  : pt1()  {
    System.out.println("编译时织入 around 增强前,"+show(thisJoinPoint));
    //这里不需要传入参数
    var obj=  proceed();
    System.out.println("编译时织入 around 增强后,"+show(thisJoinPoint));
    return "编译时织入,"+obj;
  }
   after() : pt1(){
    System.out.println("编译时织入 after 增强,"+show(thisJoinPoint));
  }
}

测试代码

package com.hank;
public class Programmer {
  public static void main(String[] args) throws Exception {
    new Bar().foo();
    /*
    编译时织入 before 增强,目标类对象:com.hank.aop.Bar@2344fc66,被增强的方法:foo传入的参数是ok
    编译时织入 around 增强前,目标类对象:com.hank.aop.Bar@2344fc66,被增强的方法:foo传入的参数是ok
    编译时织入 around 增强后,目标类对象:com.hank.aop.Bar@2344fc66,被增强的方法:foo传入的参数是ok
    编译时织入 after 增强,目标类对象:com.hank.aop.Bar@2344fc66,被增强的方法:foo传入的参数是ok
    编译时织入,foo 方法返回结果ok
    */
  }
}

编译时织入普通的 class 文件

这种用法比写 .aj 文件方便多了

定义一个切面

@Aspect
public class BarAspect {
  private String show(JoinPoint point){
   return "目标类对象:"+point.getTarget ()+
          ",被增强的方法:"+point.getSignature ().getName ()+
          "传入的参数是"+String.join(";", Arrays.stream(point.getArgs()).map(p->p.toString()).
          collect(Collectors.toList()));
  }
  @Pointcut("execution(* com.hank.aop.Bar.*(..))")
  public void pt2() {
  }
  @Before("pt2()")
  public void beforeAdvice(JoinPoint point) {
    System.out.println("编译时织入,通过 注解 配置, before 增强,"+show(point));
  }

  @SneakyThrows
  @Around("pt2()")
  public Object aroundAdvice(ProceedingJoinPoint point) {
    System.out.println("编译时织入,通过 注解 配置, around 增强前,"+show(point));
    var ret= point.proceed();
    System.out.println("编译时织入,通过 注解 配置, around 增强后,"+show(point));
    return "运行时织入,通过 注解 配置,"+ret;
  }

  @After("pt2()")
  public void afterAdvice(JoinPoint point) {
    System.out.println("编译时织入,通过 注解 配置, after 增强,,"+show(point));
  }
}

测试代码

package com.hank;
public class Programmer {
  public static void main(String[] args) throws Exception {
    new Bar().foo();
    /*
    编译时织入 before 增强,目标类对象:com.hank.aop.Bar@2344fc66,被增强的方法:foo传入的参数是ok
    编译时织入 around 增强前,目标类对象:com.hank.aop.Bar@2344fc66,被增强的方法:foo传入的参数是ok
    编译时织入 around 增强后,目标类对象:com.hank.aop.Bar@2344fc66,被增强的方法:foo传入的参数是ok
    编译时织入 after 增强,目标类对象:com.hank.aop.Bar@2344fc66,被增强的方法:foo传入的参数是ok
    编译时织入,foo 方法返回结果ok
    */
  }
}

加载的时候织入

不同于编译时的织入,会直接修改原本的 class 文件, 加载的时候织入不会修改原本的 class 文件,但是会修改加载到 jvm 里的文件 通过java agent机制在内存中操作类文件,可以不需要ajc的支持做到在JVM 中定义任何类型之前织入它们的增强代码 因为是通过java agent 机制,所以不需要在编写 .aj 文件,可以用纯 java 代码去定义增强的功能,所以不需要 ajc 编译器的支持

切面

@Aspect
public class BarAspect {
  private String show(JoinPoint point){
   return "目标类对象:"+point.getTarget ()+
          ",被增强的方法:"+point.getSignature ().getName ()+
          "传入的参数是"+String.join(";", Arrays.stream(point.getArgs()).map(p->p.toString()).
          collect(Collectors.toList()));
  }
  @Pointcut("execution(* com.hank.aop.Bar.*(..))")
  public void pt2() {
  }
  @Before("pt2()")
  public void beforeAdvice(JoinPoint point) {
    System.out.println("加载的时候织入,通过 注解 配置, before 增强,"+show(point));
  }

  @SneakyThrows
  @Around("pt2()")
  public Object aroundAdvice(ProceedingJoinPoint point) {
    System.out.println("加载的时候织入,通过 注解 配置, around 增强前,"+show(point));
    var ret= point.proceed();
    System.out.println("加载的时候织入,通过 注解 配置, around 增强后,"+show(point));
    return "加载的时候织入,通过 注解 配置,"+ret;
  }

  @After("pt2()")
  public void afterAdvice(JoinPoint point) {
    System.out.println("加载的时候织入,通过 注解 配置, after 增强,,"+show(point));
  }
}

在资源路径下建立 META-INF 文件夹,并在此文件夹下建立 aop.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<aspectj>
    <aspects>
        <!-- 切面类,此类目的是在加载的时候告诉 aspectj 切面定义在哪里 -->
        <aspect name="com.hank.aop.BarAspect"/>
    </aspects>
    <weaver options="-verbose -showWeaveInfo">
        <!-- 指定需要进行织入操作的目标类范围 -->
        <include within="com.hank.aop.*"/>
    </weaver>
</aspectj>

不需要插件支持,但是要告诉 jvm ,如下参数,目的是加载 class 文件的时候先执行如下包内的相关命令 vm 参数 -javaagent:D:\Cache\maven\org\aspectj\aspectjweaver\1.9.9.1\aspectjweaver-1.9.9.1.jar

测试代码

package com.hank;
public class Programmer {
  public static void main(String[] args) throws Exception {
    new Bar().foo();
    /*
    加载的时候织入 before 增强,目标类对象:com.hank.aop.Bar@2344fc66,被增强的方法:foo传入的参数是ok
    加载的时候织入 around 增强前,目标类对象:com.hank.aop.Bar@2344fc66,被增强的方法:foo传入的参数是ok
    加载的时候织入 around 增强后,目标类对象:com.hank.aop.Bar@2344fc66,被增强的方法:foo传入的参数是ok
    加载的时候织入 after 增强,目标类对象:com.hank.aop.Bar@2344fc66,被增强的方法:foo传入的参数是ok
    加载的时候织入,foo 方法返回结果ok
    */
  }
}

参考-说说在,Spring AOP 中如何实现类加载期织入(LTW)

Spring AOP 代理

spring aop 在 spring ioc 的基础上对上面的动态代理框架的封装,spring aop 的实现要依赖于 spring ioc,注定能进行 aop 的只能是 注入的 bean 对象,及只能进行运行时的动态代理,jdk 动态代理或 cglib 这些动态代理是一种硬编码方式的动态代理,需要代码去实现对应的接口,spring aop 对它们进行了封装,做到更好的无侵入. 默认如果使用接口的,用JDK动态代理实现,如果没有接口则使用 CGLIB 实现,也可以通过配置只使用 CGLIB 代理,但是鉴于 aspectj 的知名度,在使用的语法上有用了 aspect 使用上的那一套概念.这一套概念有 AOP 联盟提出了标准接口,目的时降低使用 aop 在技术上的复杂度.spring 也是按照这套标准. aop alliance 联盟

springAOPTwoWay

优点:

  • 使用aspectj 的语法概念,使用简单
  • 切点表达式不局限于正则表达式

缺点:

  • 只能代理非 final及静态的方法
  • 只能用于 spring ioc 容器内的 bean 对象
  • 因为是在运行时生成代码有额外的开销

例子

spring 新版本默认没有引入 aspectJ 包,需要引入此包,才能使用 aspectj 切面定义语法

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjrt</artifactId>
  <version>1.9.9.1</version>
</dependency>

public class Bar {
  public  String  foo(String name){
    return "foo 方法返回结果"+name;
  }
}

//和上面的例子相比,只是把切面类(功能增强类) 增加了一个 @Component 注解
@Component
public class AopAspect {
  private String show(JoinPoint point){
   return "目标类对象:"+point.getTarget ()+
          ",被增强的方法:"+point.getSignature ().getName ()+
          "传入的参数是"+String.join(";", Arrays.stream(point.getArgs()).map(p->p.toString()).
          collect(Collectors.toList()));
  }
  @Pointcut("execution(* com.hank.aop.Bar.*(..))")
  public void pt2() {
  }
  @Before("pt2()")
  public void beforeAdvice(JoinPoint point) {
    System.out.println("运行时织入,通过 注解 配置, before 增强,"+show(point));
  }

  @SneakyThrows
  @Around("pt2()")
  public Object aroundAdvice(ProceedingJoinPoint point) {
    System.out.println("运行时织入,通过 注解 配置, around 增强前,"+show(point));
    var ret= point.proceed();
    System.out.println("运行时织入,通过 注解 配置, around 增强后,"+show(point));
    return "运行时织入,通过 注解 配置,"+ret;
  }

  @After("pt2()")
  public void afterAdvice(JoinPoint point) {
    System.out.println("运行时织入,通过 注解 配置, after 增强,,"+show(point));
  }
}

测试例子,编译后的代码没有没有进行织入的操作,织入的操作在运行时进行的

@ComponentScan({"com.hank"})
@EnableAspectJAutoProxy
public class Programmer {
  public static void main(String[] args) throws Exception {
     var  context =new AnnotationConfigApplicationContext(Programmer.class);

     // beanDefine 登记的还是被代理对象 Bar
     Stream.of(context.getBeanDefinitionNames()).forEach(p->{
       if(p.equalsIgnoreCase("bar")){
          System.out.println("bar 注册器里 bean class:"+context.getBeanDefinition(p).
          getBeanClassName());
          //bar 注册器里 bean class:com.hank.aop.Bar
       }
    });

    // 但是实例化bean 的时候 beanName 指向了代理对象
    // 容器里既有 Bar 对象 ,也有代理对象
    var bar =(Bar) context.getBean("bar");
    System.out.println("通过 context 只能获取到代理对象:"+bar.getClass().getName());
    //通过 context 只能获取到代理对象:com.hank.aop.Bar$$EnhancerBySpringCGLIB$$cb2b574d
    bar.foo("ok");
    /*
    运行时织入,通过 注解 配置, around 增强前,目标类对象:com.hank.aop.Bar@85e6769,被增强的方法:
    foo传入的参数是ok
    运行时织入,通过 注解 配置, before 增强,目标类对象:com.hank.aop.Bar@85e6769,被增强的方法:
    foo传入的参数是ok
    运行时织入,通过 注解 配置, after 增强,,目标类对象:com.hank.aop.Bar@85e6769,被增强的方法:
    foo传入的参数是ok
    运行时织入,通过 注解 配置, around 增强后,目标类对象:com.hank.aop.Bar@85e6769,被增强的方法:
    foo传入的参数是ok
    */
  
  }
}

@Pointcut 切点

@Pointcut 注解要加在一个空方法上

execution 正则表达式匹配

正则表达式匹配的是方法参数是参考的方法原型的参数类型,而不是方法调用处实际传递的参数对象类型,正

则表达式不能使用参数,如下使用是非法的 @Pointcut(value = "execution(* com.hank.aop.Bar.*(value,name))",argNames = "value,name") public void pt(int value,String name){}

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern?) throws-pattern?) 这里问号表示当前项可以有也可以没有,其中各项的语义如下:

  • modifiers-pattern:方法的可见性,如public,protected,可选.
  • ret-type-pattern:方法的返回值类型,如int,void等,必选.
  • declaring-type-pattern:方法所在类或接口的全路径名,如com.hank.Bar, .. 匹配: hank.. 表示hank包及 hank下的子包,可选.
  • name-pattern:方法名类型,如far(),必选.
  • param-pattern:方法的参数类型,如java.lang.String,不提供表示没有参数, .. 匹配:表示 0 个或任意个参数,必选.
  • throws-pattern:方法抛出的异常类型,如 throws java.lang.Exception,多个异常用 ',' 隔开, 可选.
  • * 表示匹配通配符,匹配单个连续的词组,或者是以某个词为前缀或后缀的单词词组
    • * com.hank.*.uti.*.*() 匹配 com.hank 包下的任意包下的 uti 包下的任意类的任意方法,但是方法的参数个数是零个
    • * com.hank.*.uti.Test_*.*() 匹配 com.hank 包下的任意包下的 uti 包下的 匹配Test_开头的类的任意方法,但是方法的参数个数是零个
  • +匹配父类及子类型

例子1: * 匹配

@Pointcut("execution(* com.hank...(..))") 整个表达式可以分为四个部分:

  1. 第一个 * 号:表示返回类型, *号表示所有的类型.
  2. 包名: com.hank 表示需要拦截的包名,后面的..表示当前包和当前包的所有子包,com.hank包、子包下所有类的方法.
  3. 第二个 * 号: 表示类名,*号表示所有的类.
  4. *(..):最后这个星号表示方法名, *号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数.

例子2: * 匹配

@Pointcut("execution( * com.hank.aop.uti.Test.*(int,String,..))")

  1. 第一个 * 号:表示返回类型, *号表示所有的类型.
  2. 包名: com.hank.aop.uti,Test 表示需要拦截的类名
  3. *(int,String,..):最后这个星号表示方法名, *号表示所有的方法,后面括弧里面表示方法的参数,第一个参数是 int 类型,第二个参数是 String 类型, 后面两个句点表示任何 参数

例子3:+匹配父类及子类型

@Component("parent")
public class Parent {
  public  String  parentFoo(String name,int value ){
    return  "parentFoo 返回:"+name+","+value;
  }
}

@Component
public class Bar extends Parent {
  public  String  foo(String name,int value ){
     return  "foo 返回:"+name+","+value;
  }
  public  int  boo(String name,String value ){
    return  1;
  }
}


@Aspect
@Component
public class AopAspect {

  @Pointcut(value = "execution( String Parent+.*(..))")
  public  void pt(){}

  @SneakyThrows
  @Around(value = "pt()")
  public Object aroundAdvice(ProceedingJoinPoint pointcut) {
    System.out.println("匹配的切点:"+pointcut.getStaticPart());
    System.out.println(pointcut.getTarget().getClass().getName() + "." + pointcut.getSignature().
    getName() + ",around 增强前");
    var ret = pointcut.proceed();
    System.out.println(pointcut.getTarget().getClass().getName() + "." + pointcut.getSignature().
    getName() + ",around 增强后");
    return ret;
  }
}

@ComponentScan({"com.hank"})
@EnableAspectJAutoProxy
public class Programmer {
  public static void main(String[] args) throws Exception {
    var context = new AnnotationConfigApplicationContext(Programmer.class);
    var bean= context.getBean(Bar.class);
    System.out.println( "方法返回的结果是:"+bean.foo("ok",1));
    System.out.println( "方法返回的结果是:"+bean.parentFoo("ok",1));
    System.out.println( "方法返回的结果是:"+bean.boo("notOK","notOK"));
    System.out.println("---------------------------------------");
    var parent= (Parent)context.getBean("parent");
    System.out.println( "方法返回的结果是:"+parent.parentFoo("ok",1));;
  }
}
/*
匹配的切点:execution(String com.hank.aop.Bar.foo(String,int))
com.hank.aop.Bar.foo,around 增强前
com.hank.aop.Bar.foo,around 增强后
方法返回的结果是:foo 返回:ok,1
匹配的切点:execution(String com.hank.aop.Parent.parentFoo(String,int))
com.hank.aop.Bar.parentFoo,around 增强前
com.hank.aop.Bar.parentFoo,around 增强后
方法返回的结果是:parentFoo 返回:ok,1
方法返回的结果是:1
---------------------------------------
匹配的切点:execution(String com.hank.aop.Parent.parentFoo(String,int))
com.hank.aop.Parent.parentFoo,around 增强前
com.hank.aop.Parent.parentFoo,around 增强后
方法返回的结果是:parentFoo 返回:ok,1
*/

例子4: 匹配异常

被代理类
@Component()
public class Bar   {
  //此方法标记抛出 NullPointerException
  public  String  foo(String name,int value )throws NullPointerException{
     return  "foo 返回:"+name+","+value;
  }
  public  String  boo(String name,int value ){
    return  "boo 返回:"+name+","+value;
  }
}

//切点
@Aspect
@Component
public class AopAspect {
 
  // throws NullPointerException 匹配方法签名里抛出 NullPointerException 的
  @Pointcut(value = "execution(* *(..) throws NullPointerException)")
  public  void pt(){}

  @SneakyThrows
  @Around(value = "pt()")
  public Object aroundAdvice(ProceedingJoinPoint pointcut) {
    System.out.println("匹配的切点:"+pointcut.getStaticPart());
    System.out.println(pointcut.getTarget().getClass().getName() + "." + pointcut.getSignature().
    getName() + ",around 增强前");
    var ret = pointcut.proceed();
    System.out.println(pointcut.getTarget().getClass().getName() + "." + pointcut.getSignature().
    getName() + ",around 增强后");
    return ret;
  }
}

//测试
@ComponentScan({"com.hank"})
@EnableAspectJAutoProxy
public class Programmer {
  public static void main(String[] args) throws Exception {
    var context = new AnnotationConfigApplicationContext(Programmer.class);
    var bean= context.getBean(Bar.class);
    System.out.println( "方法返回的结果是:"+bean.foo("ok",1));
    System.out.println( "方法返回的结果是:"+bean.boo("notOK",2));
  }
}
/*
匹配的切点:execution(String com.hank.aop.Bar.foo(String,int))
com.hank.aop.Bar.foo,around 增强前
com.hank.aop.Bar.foo,around 增强后
方法返回的结果是:foo 返回:ok,1
方法返回的结果是:boo 返回:notOK,2
*/

例子5: 接口匹配

接口匹配要注意获取代理类要通过 beanName ,并且要把代理类转换为接口使用

接口及被代理类

package com.hank.aop;

public interface ServiceInterface {
    String  foo(String name,int value );
}

@Component("bar")
public class Bar implements ServiceInterface {
  @Override
  public  String  foo(String name,int value ){
     return  "foo 返回:"+name+","+value;
  }
}
//切面
@Aspect
@Component
public class AopAspect {

  //被切入的方法是接口里的方法,在 ioc 里找实现此接口的 bean,代理
  @Pointcut(value = "execution(* ServiceInterface.*(..))")
  public  void pt(){}

  @SneakyThrows
  @Around(value = "pt()")
  public Object aroundAdvice(ProceedingJoinPoint pointcut) {
    System.out.println("匹配的切点:"+pointcut.getStaticPart());
    System.out.println(pointcut.getTarget().getClass().getName() + "." + pointcut.getSignature().
    getName() + ",around 增强前");
    var ret = pointcut.proceed();
    System.out.println(pointcut.getTarget().getClass().getName() + "." + pointcut.getSignature().
    getName() + ",around 增强后");
    return ret;
  }
}
@ComponentScan({"com.hank"})
@EnableAspectJAutoProxy
public class Programmer {
  public static void main(String[] args) throws Exception {
    var context = new AnnotationConfigApplicationContext(Programmer.class);
    //通过这种方式不行  var bean= (ServiceInterface)context.getBean("Bar.class);
    //以为容器里现在能提供的是代理列,而不是被代理类,只能通过 beanName获 取被代理类
    var bean= (ServiceInterface)context.getBean("bar");
    System.out.println( "方法返回的结果是:"+bean.foo("ok",1));
  }
}

例子6: 泛型匹配

切点表达式匹配的是方法的原型,也就是定义是的方法签名

package com.hank.aop;

public class Parent {
}
public class Son extends  Parent{
}

@Component
public class Bar  {
  public  String  foo(List<? extends  Parent> t){
     return "foo"+t.toString();
  }
  
  //此方法不能被切点表达式切中
  public  String  boo(List<Son> t){
    return "boo"+t.toString();
  }
}

切点

@Aspect
@Component
public class AopAspect {
  
  //这里是匹配方法的原型里的参数
  @Pointcut(value = "execution(* com.hank.aop.Bar.*(java.util.List<? extends Parent>))")
  public  void pt(){}

  @SneakyThrows
  @Around(value = "pt()")
  public Object aroundAdvice(ProceedingJoinPoint pointcut) {
    System.out.println("匹配的切点:"+pointcut.getStaticPart());
    System.out.println(pointcut.getTarget().getClass().getName() + "." + pointcut.getSignature().
    getName() + ",around 增强前");
    var ret = pointcut.proceed();
    System.out.println(pointcut.getTarget().getClass().getName() + "." + pointcut.getSignature().
    getName() + ",around 增强后");
    return ret;
  }
}
@ComponentScan({"com.hank"})
@EnableAspectJAutoProxy
public class Programmer {
  public static void main(String[] args) throws Exception {
    var context = new AnnotationConfigApplicationContext(Programmer.class);
    var bean= context.getBean(Bar.class);
    var list=List.of(new Son());
    System.out.println(bean.foo(list));
    System.out.println(bean.boo(list));
  }
}
/*
匹配的切点:execution(String com.hank.aop.Bar.foo(List))
com.hank.aop.Bar.foo,around 增强前
com.hank.aop.Bar.foo,around 增强后
foo[com.hank.aop.Son@5c18016b]
boo[com.hank.aop.Son@5c18016b]
*/

within 匹配到类为止,类下方法自动都匹配到

within表达式的粒度为类,其参数为全路径的类名(可使用通配符),表示匹配当前表达式的所有类都将被当前方法环绕

//被代理类
@Component
public class Bar {
  public  String  foo(){
    return "Bar foo 方法返回结果";
  }
  public  String  boo(){
    return "Bar boo 方法返回结果";
  }
} 

//切面
@Aspect
@Component
public class AopAspect {
  
  //within 内填写需要拦截的类的全限定名
  @Pointcut("within(com.hank.aop.Bar)")
  public void pt2() {
  }

  @SneakyThrows
  @Around("pt2()")
  public Object aroundAdvice(ProceedingJoinPoint point) {
    System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().
    getName() + ",around 增强前");
    var ret = point.proceed();
    System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().
    getName() + ",around 增强后");
    return "运行时织入," + ret;
  }
}

测试

@ComponentScan({"com.hank"})
@EnableAspectJAutoProxy
public class Programmer {
  public static void main(String[] args) throws Exception {
    var context = new AnnotationConfigApplicationContext(Programmer.class);
    var bean= context.getBean(Bar.class);
    System.out.println(bean.foo());
    System.out.println(bean.boo());
    /*
    com.hank.aop.Bar.foo,around 增强前
    com.hank.aop.Bar.foo,around 增强后
    运行时织入,Bar foo 方法返回结果
    com.hank.aop.Bar.boo,around 增强前
    com.hank.aop.Bar.boo,around 增强后
    运行时织入,Bar boo 方法返回结果
    */
  }
}

args 只匹配方法参数,而不考虑其它因素

args表达式的作用是匹配指定参数类型和指定参数数量的方法,无论其类路径或者是方法名是什么

//切面
@Aspect
@Component
public class AopAspect {
   
   args 表达式匹配 如下匹配第一个参数是 int ,第二个参数是 String, 余下参数不限制的方法
  @Pointcut("args(int ,String,..)")
  public void pt2() {
  }

  //如果想要获取注解类型的参数
  @Pointcut("@args(arg1,arg2,..)")
  public void pt3(int arg1 ,String arg2) {
  }


  @SneakyThrows
  @Around("pt2()")
  public Object aroundAdvice(ProceedingJoinPoint point) {
    System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().
    getName() + ",around 增强前");
    var ret = point.proceed();
    System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().
    getName() + ",around 增强后");
    return "运行时织入," + ret;
  }
}

//被代理类
@Component
public class Bar {
  public  String  foo(String name,int value){
    return "Bar foo 方法返回结果";
  }
  
  //按照上面的切切入点表达式 args 只能匹配到此方法
  public  String  boo(int value,String name){
    return "Bar boo 方法返回结果";
  }
}

测试

@ComponentScan({"com.hank"})
@EnableAspectJAutoProxy
public class Programmer {
  public static void main(String[] args) throws Exception {
    var context = new AnnotationConfigApplicationContext(Programmer.class);
    var bean= context.getBean(Bar.class);
    System.out.println(bean.foo());
    System.out.println(bean.boo());
    /*
    Bar foo 方法返回结果
    com.hank.aop.Bar.boo,around 增强前
    com.hank.aop.Bar.boo,around 增强后
    运行时织入,Bar boo 方法返回结果
    */
  }
}

this和target 匹配到类或接口为止,匹配所有方法

this表示代理对象,target表示被代理对象,使用方式上比较绕,不要使用

举例说明: 接口:com.hank.aop.ServiceInterface 接口实现类:com.hank.aop.ServiceImpl

jdk生成的动态代理类:实现接口HelloService,继承Proxy类 cglib生成的动态代理类:实现接口HelloService,继承HelloServiceImpl

使用 jdk 动态代理

  • @Pointcut("this(com.hank.aop.ServiceInterface)"):匹配切点 代理对象需要实现此接口所以匹配

  • @Pointcut("this(com.hank.aop.ServiceImpl)"):不匹配切点 代理对象没有继承被代理对象,所以不匹配

  • @Pointcut("target(com.hank.aop.ServiceInterface)"):匹配切点 被代理对象实现此接口,所以匹配

  • @Pointcut("target(com.hank.aop.ServiceImpl)"):匹配切点

使用 cglib 动态代理, 用如下切点去匹配 ServiceImpl 类的方法

  • @Pointcut("this(com.hank.aop.aop.ServiceInterface)"):匹配切点 代理对象实现此接口,所以匹配
  • @Pointcut("this(com.hank.aop.ServiceImpl)"):匹配切点 代理对象继承自此对象,所以匹配
  • @Pointcut("target(com.hank.aop.ServiceInterface)"):匹配切点 被代理对象实现此接口,所以匹配
  • @Pointcut("target(com.hank.aop.ServiceImpl)"):匹配切点
@Component
public class Bar {
  public  String  foo(String name,int value){
    return "Bar foo 方法返回结果";
  }
  public  String  boo(int value,String name){
    return "Bar boo 方法返回结果";
  }
}

@Aspect
@Component
public class AopAspect {
  
  //被代理对象就是 com.hank.aop.Bar 所以匹配
  @Pointcut("target(com.hank.aop.Bar)")
  public void pt2() {
  }

  @SneakyThrows
  @Around("pt2()")
  public Object aroundAdvice(ProceedingJoinPoint point) {
    System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().
    getName() + ",around 增强前");
    var ret = point.proceed();
    System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().
    getName() + ",around 增强后");
    return "运行时织入," + ret;
  }
}

@annotation 注解匹配

使用起来更为灵活,想给那个方法加 aop 直接加注解,为了方便可以把注解加到被代理类上,则对所有方法都起作用

//定义注解,通过注解找切入点
@Target({ElementType.METHOD , ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAopAnnotationAOP {
}

//被代理类
@Component
public class Bar {

  //给需要拦截增强功能的方法添加注解,定义切入点
  @MyAopAnnotationAOP
  public  String  foo(){
    return "foo 方法返回结果";
  }

  public  String  boo(){
    return "boo 方法返回结果";
  }
}

//通过注解关联的切面
@Aspect
@Component
public class AopAspect {
  
  // @annotation里填入注解的全路径名
  @Pointcut("@annotation(com.hank.aop.MyAopAnnotationAOP)")
  public void pt2() {
  }
   
  //如果想要获取注解类型的参数
  @Pointcut("@annotation(myAopAnnotationAOP)")
  public void pt3(MyAopAnnotationAOP myAopAnnotationAOP) {
  }

  @SneakyThrows
  @Around("pt2()")
  public Object aroundAdvice(ProceedingJoinPoint point) {
    System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().
    getName() + ",around 增强前");
    var ret = point.proceed();
    System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().
    getName() + ",around 增强后");
    return "运行时织入," + ret;
  }
}

测试

@ComponentScan({"com.hank"})
@EnableAspectJAutoProxy
public class Programmer {
  public static void main(String[] args) throws Exception {
    var context = new AnnotationConfigApplicationContext(Programmer.class);
    var bean= context.getBean(Bar.class);
    System.out.println(bean.foo());
    System.out.println(bean.boo());
    /*
    com.hank.aop.Bar.foo,around 增强前
    com.hank.aop.Bar.foo,around 增强后
    运行时织入,foo 方法返回结果
    boo 方法返回结果
    */
  }
}

@within 结合了 @annotation 和 within

@within 也是匹配到类为止,但是 @within 的参数不是类名,而是自定义的注解名,此注解要加到被代理类上

@args 结合了 @annotation 和 args

@args表示使用指定注解标注的类作为某个方法的参数时该方法将会被匹配

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyArgAnnotation {
}

//注解加到类上,用此类作为参数的方法会被 aop
@MyArgAnnotation
public class Order {
}

@Component
public class Bar {

  //此方法会被代理
  public  String  foo(Order order){
    return "Bar foo 方法返回结果";
  }
  public  String  boo(){
    return "Bar boo 方法返回结果";
  }
}
@Aspect
@Component
public class AopAspect {
  
  //寻找的切入点 @args(com.hank.aop.MyArgAnnotation) 
  //先找此注解标注的类,在找 ioc 容器里找 bean 用此类作为参数的方法,此方法会被代理
  @Pointcut("@args(com.hank.aop.MyArgAnnotation)")
  public void pt2() {
  }

  @SneakyThrows
  @Around("pt2()")
  public Object around(ProceedingJoinPoint point) {
    System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().
    getName() + ",around 增强前");
    var ret = point.proceed();
    System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().
    getName() + ",around 增强后");
    return "运行时织入," + ret;
  }
}

测试

@ComponentScan({"com.hank"})
@EnableAspectJAutoProxy
public class Programmer {
  public static void main(String[] args) throws Exception {
    var context = new AnnotationConfigApplicationContext(Programmer.class);
    var bean= context.getBean(Bar.class);
    System.out.println(bean.foo(new Order()));
    System.out.println(bean.boo());
    /*
     com.hank.aop.Bar.foo,around 增强前
     com.hank.aop.Bar.foo,around 增强后
     运行时织入,Bar foo 方法返回结果
     Bar boo 方法返回结果
    */
  }
}

bean(Bean id或名字通配符)匹配 bean 的所有方法

使用“bean(Bean id或名字通配符)”匹配特定名称的Bean对象的执行方法;Spring ASP扩展的,在AspectJ中无相应概念

//被代理类
@Component("bar")
public class Bar {
  public  String  foo(String name,int value ){
     return  "foo 返回:"+name+","+value;
  }
  public  int  boo(String name,String value ){
    return  1;
  }
}

@Aspect
@Component
public class AopAspect {
  
  //也可以和通配符配合 *bar 以bar 结尾的  bean
  @Pointcut(value = "bean(bar)")
  public  void pt(){}

  @SneakyThrows
  @Around(value = "pt()")
  public Object aroundAdvice(ProceedingJoinPoint pointcut) {
    System.out.println("匹配的切点:"+pointcut.getStaticPart());
    System.out.println(pointcut.getTarget().getClass().getName() + "." + pointcut.getSignature().
    getName() + ",around 增强前");
    var ret = pointcut.proceed();
    System.out.println(pointcut.getTarget().getClass().getName() + "." + pointcut.getSignature().
    getName() + ",around 增强后");
    return ret;
  }
}

测试
@ComponentScan({"com.hank"})
@EnableAspectJAutoProxy
public class Programmer {
  public static void main(String[] args) throws Exception {
    var context = new AnnotationConfigApplicationContext(Programmer.class);
    var bean= context.getBean(Bar.class);
    System.out.println( "方法返回的结果是:"+bean.foo("ok",1));
    System.out.println( "方法返回的结果是:"+bean.boo("notOK","notOK"));
  }
}
/*
匹配的切点:execution(String com.hank.aop.Bar.foo(String,int))
com.hank.aop.Bar.foo,around 增强前
com.hank.aop.Bar.foo,around 增强后
方法返回的结果是:foo 返回:ok,1
匹配的切点:execution(int com.hank.aop.Bar.boo(String,String))
com.hank.aop.Bar.boo,around 增强前
com.hank.aop.Bar.boo,around 增强后
方法返回的结果是:1
*/

@DeclareParents 把一个方法织入到代理类中

把一个接口的方法织入到代理类中,被代理类就可以使用此方法了

//被代理类
@Component
public class Bar {
  public  String  foo(){
    return "Bar foo 方法返回结果";
  }
}

//要织入的接口及实现类
public interface OrderInterface {
  public  String  boo();
}
public class Order implements  OrderInterface{
  @Override
  public  String  boo(){
    return "Order boo 方法返回结果";
  }
}

切面

@Aspect
@Component
public class AopAspect {
  //value :指定指定的接口的方法织入给那个被代理类
  //指定被织入的方法的实现类
  @DeclareParents(value = "com.hank.*.Bar",defaultImpl = Order.class)
  //此注解只能加在属性上,属性是一个接口类
  private OrderInterface order;
}

测试

@ComponentScan({"com.hank"})
@EnableAspectJAutoProxy
public class Programmer {
  public static void main(String[] args) throws Exception {
    var context = new AnnotationConfigApplicationContext(Programmer.class);
    var bean= context.getBean(Bar.class);
     if(bean instanceof OrderInterface){
        var order= (OrderInterface)bean;
       System.out.println( order.boo());
       // Order boo 方法返回结果
     }
  }
}

切点表达式的组合 && , || , !

//被代理类
package com.hank.aop;
@Component
public class Bar {
  public  String  foo(String name,int value ){
     return  "返回:"+name+","+value;
  }
}

//切面
@Aspect
@Component
public class AopAspect {
  
  // || 或的关系,只有 execution 表达式能匹配到方法
   @Pointcut(value = "execution( * com.hank.aop.Bar.*(..)) || args(int ,int )")
  public  void pt1(){}
  
  // && 与的关系,没有方法的参数符合 args(int ,int ) ,所以没有匹配的方法
  @Pointcut(value = "execution( * com.hank.aop.Bar.*(..)) && args(int ,int )")
  public  void pt2(){}

  //切点表达式也可以组合以有的切点
  @Pointcut(value = "pt1() && pt2()")
  public  void pt3(){}

  @SneakyThrows
  @Around("pt1()")
  public Object aroundAdvice(ProceedingJoinPoint point) {
    System.out.println("匹配的切点:"+point.getStaticPart());
    System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().
    getName() + ",around 增强前");
    var ret = point.proceed();
    System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().
    getName() + ",around 增强后");
    return ret;
  }

  //因为 pt2 切点表达式没有匹配到任何方法,所以如下的增强功能没有执行
  @AfterReturning("pt2()")
  public void afterReturning(){
     System.out.println("afterReturning 增强");
  }
}
@ComponentScan({"com.hank"})
@EnableAspectJAutoProxy
public class Programmer {
  public static void main(String[] args) throws Exception {
    var context = new AnnotationConfigApplicationContext(Programmer.class);
    var bean= context.getBean(Bar.class);
    System.out.println( "方法返回的结果是:"+bean.foo("ok",1));;
  }
}
/*
匹配的切点:execution(String com.hank.aop.Bar.foo(String,int))
com.hank.aop.Bar.foo,around 增强前
com.hank.aop.Bar.foo,around 增强后
方法返回的结果是:返回:ok,1
*/

AOP常用注解及执行顺序

  • @Aspect:作用是把当前类标识为一个切面供容器读取
  • @Pointcut:切入点是对连接点进行拦截的条件定义,在程序中主要体现为书写切入点表达式,切入点表达式也可以加在各种通知上
  • @Before:标识一个前置增强方法,该注解标注的方法在业务模块代码执行之前执行,其不能阻止业务模块的执行,除非抛出异常
  • @AfterReturning:后置增强,该注解标注的方法在业务模块代码执行之后执行,如果业务代码抛出异常,此方法就不会执行了
  • @AfterThrowing:异常抛出增强,该注解标注的方法在业务模块抛出指定异常后执行
  • @After:final增强,该注解标注的方法在所有的Advice执行完成后执行,无论业务模块是否抛出异常,类似于finally的作用
  • @Around:环绕增强,该注解功能最为强大,其所标注的方法用于编写包裹业务模块执行的代码,其可以传入一个ProceedingJoinPoint用于调用 业务模块的代 码,无论是调用前逻辑还是调用后逻辑,都可以在该方法中编写,甚至其可以根据一定的条件而阻断业务模块的调用;
  • @DeclareParents:在属性声明上使用,主要用于为指定的业务模块添加新的接口和相应的实现。

执行顺序 业务代码没有错误

graph LR Around前 -.-> Before -.-> 业务 -.-> AfterReturning -.-> After -.-> Around后

业务代码有错误

graph LR Around前 -.-> Before -.-> 业务 -.-> AfterThrowing -.-> After

Spring AOP 增强方法参数 JoinPoint 及 ProceedingJoinPoint

通过这俩个 JoinPoint 及 ProceedingJoinPoint 参数获取的信息,方便在做横向拦截的时候,比如日志记录的时候就可以进行完整的记录比如什么对象的什么方法返回结果是什么

增强方法参数获取 getTarget & getThis

如下代码打印 getTarget , getThis 显示的都是 原本的 Bar 对象 com.hank.aop.Bar@2f4205be,难道被代理对象和和代理对象是一个对象码? 获取对象类型看下,被代理对象和代理对象的对象类型不一样,说明代理对象没有重写被代理对象的 toString 方法.

@Component
public class Bar {
  public  Bar() {
    System.out.println(this);
    //com.hank.aop.Bar@2f4205be
    //构造器执行的时候,还没没有生成代理对象
  }
  public  String  foo(){
    return "Bar foo 方法返回结果";
  }
}

@Aspect
@Component
public class AopAspect {

  @Pointcut("execution( * com.hank.aop.Bar.*(..))")
  public  void pt(){}

  @Before("pt()")
  public  void before(JoinPoint joinPoint){
    System.out.println(joinPoint.getTarget());
    System.out.println(joinPoint.getThis());
    //com.hank.aop.Bar@2f4205be
    //com.hank.aop.Bar@2f4205be

    System.out.println(joinPoint.getTarget().getClass().getName());
    System.out.println(joinPoint.getThis().getClass().getName());
    //com.hank.aop.Bar
    //com.hank.aop.Bar$$EnhancerBySpringCGLIB$$602e8f0e
  }
}

增强方法参数获取 getSignature

@Component
public class Bar {
  public  String  foo(int value ){
    return "Bar foo 方法返回结果";
  }
}

@Aspect
@Component
public class AopAspect {

  @Pointcut("execution( * com.hank.aop.Bar.*(..))")
  public  void pt(){}

  @Before("pt()")
  public  void before(Pointcut pointcut){
    // joinPoint.getSignature() 方法签名获取

    //获取方法定义的类的类型
    System.out.println(joinPoint.getSignature().getDeclaringType());
    //方法的名字
    System.out.println(joinPoint.getSignature().getName());

    //如果获取方法的参数  返回值呢? 直接获取没有对应的 api ,看一下 joinPoint.getSignature() 对象的接口类型
    var list =Arrays.stream(joinPoint.getSignature().getClass().getInterfaces()).map(p->p.getName())
    .collect(Collectors.toList());
    System.out.println(Arrays.toString(list.toArray()));
    //org.aspectj.lang.reflect.MethodSignature

    //把签名对象转换为此接口
    var methodSignature=(MethodSignature)joinPoint.getSignature();
    //获取返回类型
    System.out.println(methodSignature.getMethod().getReturnType());
    //class java.lang.String

    //获取参数类型
    Arrays.stream(methodSignature.getMethod().getParameterTypes()).forEach(p->{
      System.out.println(p);
    });
    //int

    //直接获取的参数类型是 object,如果参数类型是值类型,就是被包装过的. 所以不如上面的 MethodSignature 
    //接口获取的参数类型还用
    Arrays.stream(joinPoint.getArgs()).forEach(p->{
      System.out.println(p.getClass());
    });
    //class java.lang.Integer
  }
}

ProceedingJoinPoint , (@AfterReturning,@AfterThrowing) 绑定

注意点:

  • ProceedingJoinPoint 执行 proceed 方法时也可以重新给方法传递新的参数
  • 执行 proceed 不要把异常在往外部抛出
  • 如果要在 @AfterReturning(value = "pt()",returning = "ret") ,@AfterThrowing(value = "pt()",throwing = "e") ,这俩个增强方法里获取到参数要在注解里增加相应的绑定,比如 returning = "ret"
//被代理类
@Component
public class Bar {
  public  String  foo(int value ){
    //抛出错误
    throw new NullPointerException("空指针");
  }
}

//切面
@Aspect
@Component
public class AopAspect {

  @Pointcut("execution( * com.hank.aop.Bar.*(..))")
  public  void pt(){}

  @Around("pt()")
  public Object around(ProceedingJoinPoint joinPoint)  {
    //这里的 try catch 是给增强方法自己使用的,被代理类的方法执行错误捕获是在代理类类,不是这里的 try catch
    try {
      //此方法默认不用传递参数
      //如果传递参会,就会覆盖在外部调用处传递进来的参数
      var ret = joinPoint.proceed(new Object[]{2});
      return ret;
    } catch (Throwable e) {
      return null;
    }
  }

  //没有异常被抛出此方法才会被调用
  @AfterReturning(value = "pt()",returning = "ret")
  public  void afterReturning(Object ret){
      System.out.println("afterReturning advice:"+ret);
  }

  //有异常抛出的时候调用
  @AfterThrowing(value = "pt()",throwing = "e")
  public  void throwError(Exception e){
    System.out.println("throwError advice:"+e.getMessage());
    //throwError:空指针
  }


  // 此通知无论是否有异常抛出,都会被调用,也就是在被 spring AOP 在内部代理对象的 finally 方法被回调的
  @After(value = "pt()")
  public  void after(){
    System.out.println("after advice");
  }
}
@ComponentScan({"com.hank"})
@EnableAspectJAutoProxy
public class Programmer {
  public static void main(String[] args) throws Exception {
    var context = new AnnotationConfigApplicationContext(Programmer.class);
    var bean= context.getBean(Bar.class);
    System.out.println( "方法返回的结果是:"+bean.foo(1));
    //方法返回的结果是:null
  }
}
// 输出的结果:
// throwError:空指针
// after
// 方法返回的结果是:null

args() , @annotation 配合 argNames

* 代理对象 ( this)、目标对象 ( target) 和注解 ( @within、 @target、@annotation和@args) 都是可以和 argNames 进行配合的*

args() 配合 argNames

@Pointcut(value = "args( arg1,arg2 )")
public  void pt(String arg1,int arg2){}

切点表达式参数 (String arg1,int arg2) 接收的外部调用被代理类相应方法被调用时传递进来的参数,是按照顺序传递的,此参数的类型(String ,int )是要去匹配被代理方法的参数, args( arg1,arg2 ) 定义了传递进来参数的接收名字. 直接定义成 args( String,int ) 也可以匹配到被切入的方法,但是在增强方法里不能 @Around(value = "pt(p1,p2)",argNames = "pointcut,p1,p2") public Object around(ProceedingJoinPoint pointcut,String p1,int p2) 在增强方法里传递参数给 around 方法

//被代理类
package com.hank.aop;

@Component
public class Bar {
  public  String  foo(String name,int value ){
     return  "foo 返回:"+name+","+value;
  }
  
  //此方法不能被切点表达式切中
  public  String  boo(String name,String value ){
    return  "boo 返回:"+name+","+value;
  }
}

//切面
@Aspect
@Component
public class AopAspect {

  @Pointcut(value = "args( arg1,arg2 )")
  public  void pt(String arg1,int arg2){}
  

  //arg1,arg2 到 p1,p2 参数是按照顺序传递的.
  @SneakyThrows
  @Around(value = "pt(p1,p2)",argNames = "pointcut,p1,p2")
  public Object around(ProceedingJoinPoint pointcut,String p1,int p2) {
    var args =pointcut.getArgs();
    //getArgs 获取的参数是 Object 类型
    
    System.out.println("匹配的切点:"+pointcut.getStaticPart());
    System.out.println("传入的参数是"+p1+","+p2);
    System.out.println(pointcut.getTarget().getClass().getName() + "." + pointcut.getSignature().
    getName() + ",around 增强前");
    var ret = pointcut.proceed();
    System.out.println(pointcut.getTarget().getClass().getName() + "." + pointcut.getSignature().
    getName() + ",around 增强后");
    return ret;
  }
}

测试

@ComponentScan({"com.hank"})
@EnableAspectJAutoProxy
public class Programmer {
  public static void main(String[] args) throws Exception {
    var context = new AnnotationConfigApplicationContext(Programmer.class);
    var bean= context.getBean(Bar.class);
    System.out.println( "方法返回的结果是:"+bean.foo("ok",1));;

    System.out.println( "方法返回的结果是:"+bean.boo("notOK","notOK"));;
  }
}
/*
匹配的切点:execution(String com.hank.aop.Bar.foo(String,int))
传入的参数是ok,1
com.hank.aop.Bar.foo,around 增强前
com.hank.aop.Bar.foo,around 增强后
方法返回的结果是:foo 返回:ok,1
方法返回的结果是:boo 返回:notOK,notOK
*/

@annotation 配合 argNames

@Target({ElementType.METHOD , ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAopAnnotationAOP {
}

@Component
public class Bar {

  //给需要拦截增强功能的方法添加注解,定义切入点
  @MyAopAnnotationAOP
  public  String  foo(){
    return "foo 方法返回结果";
  }

  public  String  boo(){
    return "boo 方法返回结果";
  }
}

切面

@Aspect
@Component
public class AopAspect {

  //如果想要获取注解类型的参数
  @Pointcut(value = "@annotation(myAopAnnotationAOP)", argNames =  "myAopAnnotationAOP")
  public void pt(MyAopAnnotationAOP myAopAnnotationAOP) {
  }

  @SneakyThrows
  @Around(value = "pt(myAopAnnotationAOP)",argNames = "point,myAopAnnotationAOP")
  public Object aroundAdvice(ProceedingJoinPoint point,MyAopAnnotationAOP myAopAnnotationAOP) {
    System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().
    getName() + ",around 增强前");
    var ret = point.proceed();
    System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().
    getName() + ",around 增强后");
    System.out.println(myAopAnnotationAOP.annotationType());
    return "运行时织入," + ret;
  }
}

测试

@ComponentScan({"com.hank"})
@EnableAspectJAutoProxy
public class Programmer {
  public static void main(String[] args) throws Exception {
    var context = new AnnotationConfigApplicationContext(Programmer.class);
    var bean= context.getBean(Bar.class);
    System.out.println(bean.foo());
    System.out.println(bean.boo());
    /*
    com.hank.aop.Bar.foo,around 增强前
    com.hank.aop.Bar.foo,around 增强后
    interface com.hank.aop.MyAopAnnotationAOP
    运行时织入,foo 方法返回结果
    boo 方法返回结果
    */
}

多个切面执行顺序

多个切面执行顺序

默认是安装切面类名的unicode编码顺序执行的,小的先执行,如果需要强制执行顺序有俩种方法,也是安装数字小的先执行:

  1. 切面类加注解 @Order()
  2. 实现 Ordered 接口
@Component
public class Bar {
  public  String  foo(){
    return "foo 方法返回结果";
  }
}
//切面
@Aspect
@Component
@Order(1)
public class AopAspect1 {

  @Pointcut(value = "execution(* Bar.*(..))")
  public void pt() {
  }

  @SneakyThrows
  @Around(value = "pt()")
  public Object aroundAdvice(ProceedingJoinPoint point) {
    System.out.println("AopAspect1,around 增强前");
    var ret = point.proceed();
    System.out.println("AopAspect1,around 增强后");
    return "AopAspect1," + ret;
  }
}

@Aspect
@Component
public class AopAspect implements Ordered {

  @Pointcut(value = "execution(* Bar.*(..))")
  public void pt() {
  }

  @SneakyThrows
  @Around(value = "pt()")
  public Object aroundAdvice(ProceedingJoinPoint point) {
    System.out.println("AopAspect,around 增强前");
    var ret = point.proceed();
    System.out.println("AopAspect,around 增强后");
    return "AopAspect," + ret;
  }

  @Override
  public int getOrder() {
    return 2;
  }
}

测试

@ComponentScan({"com.hank"})
@EnableAspectJAutoProxy
public class Programmer {
  public static void main(String[] args) throws Exception {
    var context = new AnnotationConfigApplicationContext(Programmer.class);
    var bean= context.getBean(Bar.class);
    System.out.println(bean.foo());
    /*
    AopAspect1,around 增强前
    AopAspect,around 增强前
    AopAspect,around 增强后
    AopAspect1,around 增强后
    AopAspect1,AopAspect,foo 方法返回结果
    */
  }
}

单个切面内的执行顺序

单个切面内的同一功能的增强器执行顺序,是按照 unicode编码顺序执行的,小的先执行

@Component
public class Bar {
  public  String  foo(){
    return "foo 方法返回结果";
  }
}

@Aspect
@Component
public class AopAspect  {

  @Pointcut(value = "execution(* Bar.*(..))")
  public void pt() {
  }

  @SneakyThrows
  @Around(value = "pt()")
  public Object b(ProceedingJoinPoint point) {
    System.out.println("b,around 增强前");
    var ret = point.proceed();
    System.out.println("b,around 增强后");
    return "AopAspect," + ret;
  }

  @SneakyThrows
  @Around(value = "pt()")

  public Object a(ProceedingJoinPoint point) {
    System.out.println("a,around 增强前");
    var ret = point.proceed();
    System.out.println("a,around 增强后");
    return "AopAspect," + ret;
  }
}

@ComponentScan({"com.hank"})
@EnableAspectJAutoProxy
public class Programmer {
  public static void main(String[] args) throws Exception {
    var context = new AnnotationConfigApplicationContext(Programmer.class);
    var bean= context.getBean(Bar.class);
    System.out.println(bean.foo());
    /*
    a,around 增强前
    b,around 增强前
    b,around 增强后
    a,around 增强后
    AopAspect,AopAspect,foo 方法返回结果
    */
  }
}

代理对象调用自身的方法

如下代码在被代理对象内方法调用方法 var str =this.doo(); doo方法 没有被增强,是因为方法想要增强必须是被代理对象调用,而不是被被代理对象调用

//被代理对象
@Component
public class Bar {
  public  String  foo(){
    //被代理对象调用的方法不会被增强
    var  str =this.doo();
    return "foo 方法返回结果,"+str;
  }

  public  String doo(){
     return  "doo";
  }
}

//切面
@Aspect
@Component
public class AopAspect  {

  @Pointcut(value = "execution(* Bar.*(..))")
  public void pt() {
  }

  @SneakyThrows
  @Around(value = "pt()")
  public Object around(ProceedingJoinPoint point) {
    System.out.println("around,around 增强前");
    var ret = point.proceed();
    System.out.println("around,around 增强后");
    return "AopAspect," + ret;
  }
}
@ComponentScan({"com.hank"})
@EnableAspectJAutoProxy
public class Programmer {
  public static void main(String[] args) throws Exception {
    var context = new AnnotationConfigApplicationContext(Programmer.class);
    var bean= context.getBean(Bar.class);
    System.out.println(bean.foo());
    /*
    around,around 增强前
    around,around 增强后
    AopAspect,foo 方法返回结果,doo
    */
  }
}

那如何在被代理对象内获取代理对象呢?解决方法:

  1. 注入代理对象
    @Component
    
     // 注入代理对象
     public class Bar {
    
       @Autowired
       private Bar bar;
       public  String  foo(){
         var  str =bar.doo();
         return "foo 方法返回结果,"+str;
       }
    
       public  String doo(){
         return  "doo";
       }
     }
    
  2. 更方便的方法是 用AopContext 上下文 获取代理对象 默认 AopContext 上下文是没有对外赋值的,如果要赋值要开启 @EnableAspectJAutoProxy(exposeProxy = true)
    @Component
     public class Bar {
    
       public  String  foo(){
         var  str = ((Bar)AopContext.currentProxy()).doo();
         return "foo 方法返回结果,"+str;
       }
    
       public  String doo(){
         return  "doo";
       }
     }
    

Spring AOP SCOPE_SINGLETON vs SCOPE_PROTOTYPE

代理对象的 SCOPE 是跟着被代理对象的 SCOPE
//被代理对象是  PROTOTYPE
@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class Bar {
  public Bar(){
    System.out.println("Bar 构造函数执行");
  }
  public  String  foo(){
    return "foo 方法返回结果";
  }
}

//切面默认是单例
@Aspect()
@Component
public class AopAspect  {

  public AopAspect(){
     System.out.println("AopAspect 执行一次-----------------------------");
  }

@Pointcut(value = "execution(* Bar.*(..))")
  public void pt() {
  }

  @SneakyThrows
  @Around(value = "pt()")
  public Object around(ProceedingJoinPoint point) {
    System.out.println("around,around 增强前");
    var ret = point.proceed();
    System.out.println("around,around 增强后");
    return "AopAspect," + ret;
  }
}


  @ComponentScan({"com.hank"})
  @EnableAspectJAutoProxy(exposeProxy = true)
  public class Programmer {
  public static void main(String[] args) throws Exception {
  var context = new AnnotationConfigApplicationContext(Programmer.class);
  var bean1= context.getBean(Bar.class);
  System.out.println("代理对象的 hashCode: "bean1.hashCode());
  System.out.println(bean1.foo());

  var bean2=context.getBean(Bar.class);
  System.out.println("代理对象的 hashCode: "bean2.hashCode());
  System.out.println(bean2.foo());
  }
  /*
  AopAspect 执行一次-----------------------------
  Bar 构造函数执行
  代理对象的 hashCode:773512481
  around,around 增强前
  around,around 增强后
  AopAspect,foo 方法返回结果
  Bar 构造函数执行
  代理对象的 hashCode: 800359709
  around,around 增强前
  around,around 增强后
  AopAspect,foo 方法返回结果
  
  */
}
Aspect 对象的 SCOPE_PROTOTYPE 是跟着被代理对象的 SCOPE_PROTOTYPE

Aspect 对象的 SCOPE 是 PROTOTYPE ,必须代理对象的 SCOPE 必须也是 PROTOTYPE, 单独 Aspect 对象的 SCOPE 是 PROTOTYPE 没有作用

@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class Bar {

  public Bar(){
    System.out.println("Bar 构造函数执行");
  }
  public  String  foo(){
    return "foo 方法返回结果";
  }
}

@Aspect()
@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class AopAspect  {

  public AopAspect(){
    System.out.println("AopAspect 执行一次-----------------------------");
  }

  @Pointcut(value = "execution(* Bar.*(..))")
  public void pt() {
  }

  @SneakyThrows
  @Around(value = "pt()")
  public Object around(ProceedingJoinPoint point) {
    System.out.println("around,around 增强前");
    var ret = point.proceed();
    System.out.println("around,around 增强后");
    return "AopAspect," + ret;
  }
}

@ComponentScan({"com.hank"})
@EnableAspectJAutoProxy(exposeProxy = true)
public class Programmer {
  public static void main(String[] args) throws Exception {
    var context = new AnnotationConfigApplicationContext(Programmer.class);
    var bean1= context.getBean(Bar.class);
    System.out.println("代理对象的 hashCode: "+bean1.hashCode());
    System.out.println(bean1.foo());

    var bean2=context.getBean(Bar.class);
    System.out.println("代理对象的 hashCode: "+bean2.hashCode());
    System.out.println(bean2.foo());
  }
}
/*
代理对象的 hashCode: 818388744
AopAspect 执行一次-----------------------------
around,around 增强前
around,around 增强后
AopAspect,foo 方法返回结果
Bar 构造函数执行

代理对象的 hashCode: -1106177088
AopAspect 执行一次-----------------------------
around,around 增强前
around,around 增强后
AopAspect,foo 方法返回结果
*/
👍🎉🎊