Spring AOP 切点指示器

如题所述

第1个回答  2022-06-24

Spring AOP借助AspectJ的切点表达式语言来定义Spring切面,下面是切点表达式中使用的指示器:

下面将通过一些测试案例来说明以上各个切点指示器的用法。另外 execution 不作额外说明,因为比较常见。

定义一个水果接口

定义一个甜水果类

定义一个产地接口

定义一个水果重量接口

定义一个苹果重量类

定义一个红苹果类

定义一个果农类

使用的测试类
SweetFruit 实现 Fruit 接口。 RedApple 继承 SweetFruit 并且实现 Origin 接口,并且关联 FruitWeight 接口。 AppleWeight 实现 FruitWeight 接口。

定义包含 within 指示器的切面

测试用例

within 指示器用来限定连接点必须在确定的类型或包中 ,在上面的切面中定义了 Fruit 、 SweetFruit 、 RedApple 这三个类型,根据输出发现只有 SweetFruit 、 RedApple 这两个切面进行了拦截。这说明了 within 指示器只拦截确定的类型,也就是跟它的接口无关,定义什么类型就拦截什么类型。

定义包含 this 指示器的切面

测试用例

this 指示器用来限定连接点属于给定类型的一个实例(代理对象) ,从上面的输出中可以看到, sweetFruit.print(); 被 this(...Fruit) 、 this(...SweetFruit) 拦截, redApple.print(); 和 redApple.printOrigin(); 被 this(...Fruit) 、 this(...SweetFruit) 、 this(...RedApple) 、 this(...Origin) 拦截。这说明了不管方法是来源于哪个接口或类( redApple.printOrigin(); 来源于 Origin 接口),只要代理对象的实例属于 this 中所定义的类型,那么这个方法就会被拦截。比如 sweetFruit 的代理对象既是 Fruit 的一个实例,也是 SweetFruit 的一个实例。 redApple 的代理对象分别属于 Fruit 、 SweetFruit 、 RedApple 、 Origin 这四个类型的一个实例。
注:我这边的测试环境显示AOP使用了CGLIB代理,也就是继承代理,所以代理对象同属以上接口或类,如果使用了JDK动态代理可能会产生不同的结果

定义包含 target 指示器的切面

测试用例

target 指示器用来限定连接点属于给定类型的一个实例(被代理对象) ,这个指示器的语义跟 this 指示器是很相似的,然后从上面的输出来看它们也是一样的(除了指示器不同)。但是它们有一个重要的区别, this 中的实例指的是代理对象,而 target 中的实例指的是被代理对象。

定义包含 args 指示器的切面 ,上面的切面表达式中额外定义了 within 指示器,这个主要是为了缩小 args 的使用范围。如果不加,Spring AOP会尝试去代理所有符合条件的对象,但是有些对象的访问会有限制,导致启动异常。这个也提醒我们使用AOP时必须要明确指定使用范围,否则会造成不可预料的错误。

测试用例 ,回顾一下之前定义的三个 printPrice 方法:

args 指示器用来限定连接点,也就是方法执行时它的参数属于给定类型的一个实例 ,从上面的输出来看,只有 public void printPrice(Integer price, String mesg) 这个方法被拦截,因为只有它符合条件。如果我们将定义改为 public void printPrice(String mesg,Integer price ) ,结果如下:

也就是说 args 指示器不但对参数类型有要求,而且还会对参数个数、定义顺序有要求。

定义包含 @target 指示器的切面

测试用例 ,然后回顾一下之前定义的 RedApple 这个类上所注解的 @Validated :

现在删除 RedApple 上的 @Validated 注解,将这个注解放到 SweetFruit 上,输出如下:

重复以上步骤,将 @Validated 注解放到 Fruit 接口上,输出如下:

@target 用来限定连接点属于一个执行对象(被代理对象)所属的拥有给定注解的类。 虽然 @target 和 target 名称相似,但是它们用法是完全不同的,前者针对注解,后置针对类型。另外从上面的输出可以看出 @target 限定在一个执行对象的所属类,与它的父类接口无关。

定义包含 @within 指示器的切面

测试用例 ,然后注意 redApple.print(11); 它是定义在 SweetFruit 类中

现在删除 RedApple 上的 @Validated 注解,将这个注解放到 SweetFruit 上,输出如下:

重复以上步骤,将 @Validated 注解放到 Fruit 接口上,输出如下:

@within 用来限定连接点属于拥有给定注解的类型中。 从上面的输出可以看出, @target 针对执行对象所属的类,而 @within 针对执行对象所属的类型。另外所执行的方法必须属于拥有给定注解的类型中,比如 redApple.print(); 被重写在 RedApple 类中, redApple.print(11); 被定义在 SweetFruit 类中,当这两个类拥有指定注解后方法执行时才会被拦截。最后需要注意的是 @within 和 within 只是名称接近,实际使用效果是不同的。

定义包含 @args 指示器的切面

测试用例 ,回顾一下在 RedApple 中定义的方法以及 FruitWeight 接口的定义:

现在删除 AppleWeight 上的 @JsonDeserialize 注解,将这个注解放到 FruitWeight 上,输出如下:

现在将 @JsonDeserialize 注解放到 AppleWeight 上,并且再定义一个继承了 AppleWeight 的子类:

测试用例

@args 用来限定连接点,方法执行时传递的参数的运行时类型拥有给定注解。 根据以上的输出以及 @args 的定义,这个给定的注解要么定义在方法参数的类型本身上,那么定义在的它的实现类上。比如 RedAppleWeight 它虽然继承了 AppleWeight (拥有指定注解),但是它本身没有指定注解,并且当 FruitWeight 也没有指定注解时,相关方法不会被拦截。

定义包含 @annotation 指示器的切面

测试用例 ,回顾一下 FruitFarmer 的定义:

现在删除 print() 上的 @JsonSerialize 注解,输出如下:

@annotation 用来限定连接点的方法拥有给定注解。 这个指示器比较容易理解,就是目标方法上拥有给定注解就可以了。

参考资源