Spring AOP 在@Aspectj形式下的Pointcut

今天我们先来学习一下Spring基于Aspectj的AOP实现。我想先从实战开始先,理论知识那些就有点重复轮子了,虽然这也是,但对于自己在实战中还是很用处的,也希望帮助到大家。正式开始!

@AspectJ形式下的SpringAOP

支持Aspecj5发布的@AspectJ形式的AOP实现的方式。现在,我们可以直接使用POJO来定义Aspect以及相关的Aspect定义,并将其声明的横切逻辑织入当前系统。但是,虽然Spring AOP继承了AspectJ,但实际上只能说是仅仅拿来AspectJ的“皮大衣”用一下。而底层各种概念的实现以及织入方式,依然使用的是Spring原先的实现体系。


入门案例

基于以前的AOP

	public class AfterAdvice  implements AfterReturningAdvice {
	@Override
	public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
		System.out.println("afterReturning");
	}
}

	public class BeforeAdvice implements MethodBeforeAdvice {
	@Override
	public void before(Method method, Object[] args, Object target) throws Throwable {
		System.out.println("before");
	}
}

	public class UserServiceImpl  {

	public User createUser(String firstName, String lastName, int age) {
		User user = new User();
		user.setFirstName(firstName);
		user.setLastName(lastName);
		user.setAge(age);
		return user;
	}

	public User queryUser() {
		System.out.println("本方法");
		User user = new User();
		user.setFirstName("test");
		user.setLastName("test");
		user.setAge(20);
		return user;
	}
}

在这里插入图片描述
在这里插入图片描述
我知道你们没有认真看,都SpringBoot了都还搞这些配置,来嘛,让我们进去实战

基于@AspectJ

@Aspect
@Component
public class AspectTest {

	@Pointcut(value = "execution(* aop.aspect.*.*(..))")
	public void pointCut() {

	}

	@Around(value = "pointCut()")
	public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
		System.out.println("around....执行前");
		proceedingJoinPoint.proceed();
		System.out.println("around....执行后");
	}
	@Before(value = "pointCut()")
	public void before() {
		System.out.println("before....执行");
	}

	@AfterReturning(value = "pointCut()")
	public void afterReturning(JoinPoint joinPoint) {
		System.out.println(joinPoint);
		System.out.println("AfterReturning....执行");
	}
	@AfterThrowing(value = "pointCut()")
	public void afterThrowing() {
		System.out.println("AfterThrowing....执行");
	}
	@After(value = "pointCut()")

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


}

在这里插入图片描述
开启通过扫码的方式,管理bean,开启自动注入AOP。就这么简单。
在这里插入图片描述
接下来我们通过代码逐一分析一下代码中出现过得一下注解,并配合代码说明一些还没有用到的。


@Pointcut

在这里插入图片描述
通过@Pointcut定义一个切面,它就会适配全有符合的方法,因为Spring只支持方法执行类型的Joinpoint,奉行着Keep It Simple,Stupid原则。仅付出20%的努力,就能获得80%的回报。
接下来分析如何定义一个好的Pointcut。

AspectJ指示器描述
execution用于匹配是连接点的执行方法
within限制连接点匹配指定的类型
this限制连接点匹配AOP代理的Bean引用未指定类型的类
target限制连接点匹配目标对象为指定类型的类
args限制连接点匹配参数未指定类型的执行方法
@within限制连接点匹配指定注解所编著的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类里)
@target限制连接点匹配特定的执行对象,这些对象对应的类要具备指定类型的注解
@args限制连接点匹配参数未指定类型的执行方法
@annotation限制匹配带有指定注解连接点

在spring中尝试使用AspectJ其他指示器时,将会抛出IllegalArgumentException异常。

当我们查看上面展示的这些spring支持的指示器时,注意只有execution指示器是唯一的执行匹配,而其他的指示器都是用于限制匹配的。这说明execution指示器是我们在编写切点定义时最主要使用的指示器,在此基础上,我们使用其他指示器来限制所匹配的切点。


execution (用于匹配是连接点的执行方法)

execution标志符规定格式

execution: ( [modifiers-pattern] ret-type-pattern [declaring-type-pattern] name-pattern(param-pattern) [throw-pattern] )
中文翻译:
execution:( [方法修饰符] 方法返回类型 [路径] 方法名(参数类型)[抛出错误类型] )

用[ ]括住的是可有可无,跟操作Redis用意一样
可以简化成 execution:( 方法返回类型 方法名(参数类型) )


通过几条execution语句分析它是匹配了什么?

execution(public void doSomething(String))
// 适配全有 public void doSomethine(String arg) {} 的执行方法

execution ( String org.com.ccn.CCN.doSomething(…))
// 适配在包路径下org.com.ccn下类CCN的 public StringdoSomethine(什么都可以) {} 的执行方法

除此之外,我们还可以在execution的表达式使用两种通配符, 即 * 和…。

  • * : 可以用来任何部分的匹配模式中,可以匹配相邻的多个字符。
  • .. : 通配符可以在两个位置使用,一个在[路径],一个在方法参数匹配模式下的位置使用 方法名(参数类型).

可以简化成 execution:( 方法返回类型 方法名(参数类型) )

execiton ( * * (String)) //返回类型不管 方法名不管(参数String)的执行方法

execiton ( * * (*)) //返回类型不管 方法名不管( 一个 参数类型不管)的执行方法

execiton ( * * (…)) //返回类型不管 方法名不管( 0到多个 参数类型不管)的执行方法

execiton (void com.ccn.*.doSomething(*))
//返回类型viod 包路径com.ccn下全部类 方法名doSomething( 一个 参数类型不管)的执行方法

execiton (void com.ccn..*.doSomething(*))
//返回类型viod 包路径com.ccn下全部类以及com.ccn下层包下的全部类型 方法名doSomething( 一个 参数类型不管)的执行方法

within (限制连接点匹配指定的类型)

在这里插入图片描述
如图上面的within ,含义将只会匹配包路径aop.aspect下的Target这个类的全部的方法。另外,我们也可以通过 * 和 ..通配符来扩展匹配类型的范围。如下所示

within( com.ccn.aop.target.*) //匹配com.ccn.aop.target包下所有类型的方法级别的Joinpoint

within( com.ccn.aop.Target+) //匹配com.ccn.aop.Target类以及子类所有类型的方法级别的Joinpoint


在这里我们要注意,就算适配了全部的方法也不是都可以通过增强的,我们要知道Spring底层还是用JDK动态代理 和 Cglib 来实现AOP。也就是说基于JDK动态代理时候,不是接口方法无法增强,基于Cglib代理的时候,final方法无法增强

within( com.ccn.aop.target..*) //匹配com.ccn.aop.target包下以及子包下所有类型的方法级别的Joinpoint


this (限制连接点匹配AOP代理的Bean引用指定类型的类) & target (限制连接点匹配目标对象为指定类型的类)

实际上,从代理模式来看,代理对象通常跟目标对象的类型是相同的,因为目标对象与它的代理对象实现同一个接口。即使使用CGLIB的方式,目标对象的代理对象属于目标对象的子类型,通过单独使用this或者target指定类型,起到的限定作用其实是差不多的。所以这里关乎到代理的类型。我们知道JDK动态代理是返回Proxy实现接口的代理类,然后Cglib是返回子类作为代理类那么这会造成什么结果呢?看如下代码。

	public interface ProxyInterface {
		void doSomething();
	}
	
	@Service
	public class TargetFoo implements ProxyInterface{

	@Override
	public void doSomething() {
		System.out.println("本方法");
	}
}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
我们发现增强都没有问题,改成Cglib试一下
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
cglib增强也是没有问题的。毕竟不管是JDK动态代理,还是Cglib都是直接或者间接实现了ProxyInterface这个接口。(JDK动态代理直接实现了该接口,Cglib继承TargetFoo间接实现了该接口)。 所以这两者的都同时满足 this 和 target的条件。但是如果换成是目标对象呢?结果又会如何?

在这里插入图片描述
在这里插入图片描述
cglib的情况下,方法继续被增强。再看看JDK动态代理的结果
在这里插入图片描述
jdk动态代理的结果是target标志符被匹配了,然后this标志符没有被匹配。为什么呢?

结论

在这里插入图片描述
不管是Cglib 不管this和target是基于目标类还是实现类。都是可以匹配到的。毕竟别人实打实的继承了你要增强的对象。然后对于JDK动态代理来说,如果this和target都是接口,那没有什么好说的,毕竟JDK动态代理就是实现同样的接口。重点: 那么在this和target基于目标类的情况下,JDK动态代理==this不触发,target触发。==的结果,参照上图的理解,因为jdk的Proxy只实现了接口跟目标类没有啥瓜葛所以this肯定是不会执行的,那么target为什么会执行呢? 这里就引发了我理解的target的用意。target是匹配你还未增强的对象的,this是匹配你增强过后的对象的。 浅显易懂

args (限制连接点匹配参数未指定类型的执行方法)

该标志符的作用是,帮助我们捕捉拥有拥有指令参数类型、指定参数数量的方法级Joinpoint,而不管该方法在什么类型中被声明
在这里插入图片描述
那么如下两个方法都会被匹配到

	public class Foo {
	public void doSomethine(Target target) {...}
	}
	public class Bar {
	public String doSomethine(Target target) {...}
	}

因为args标志符会在运行期间动态检查参数的类型,即使我们的方法签名声明如下:

	public boolean login(Object target) {...}

只要传入的是Target类型的实例,那么使用args标志符的Pointcut表达式依然可以捕捉到该Joinpoint。
但是类似于execution( * * (User))这样的Pointcut表达式,则无法捕捉以上的方法,因为它是静态的。

不知道大家到了这里,会不会有个疑惑,那么子类呢?子类能匹配吗
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们发现其实是没有问题的,也就是说代理类的参数也没有问题,毕竟也是继承类或实现接口。



不带@ 都讲完了,接下来都是带着@的标志符,带了@其实就是跟接口相关的增强了,因此指定的类型也必须为接口类型作为必要前提


@within (限制连接点匹配指定注解所编著的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类里))

如果使用@within指定了某种类型的注解,那么,只要对象标注了该类型的注解,使用了@within标志符的Pointcut表达式将会匹配该对象内部全有Joinpoint(方法)


	@Retention(RetentionPolicy.RUNTIME)
	@Target({ElementType.METHOD,ElementType.TYPE})
	public @interface AnyJoinPointAnnotation {
	}
	@AnyJoinPointAnnotation 
	public class Foo {
		public void mothod1() {...};
		public void mothod2() {...};
		.
		.
		.
		
	}

在这里插入图片描述
这样就大功告成了,method1,method2等方法将全部被该Pointcut表达式说匹配。

@target (限制连接点匹配特定的执行对象,这些对象对应的类要具备指定类型的注解)

使用方式同上


@args (限制连接点匹配参数未指定类型的执行方法)

还是用回上面的代码。
在这里插入图片描述
当参数类型的类被该注解标明了,那么该方法才会被增强。伪如下

	public class CCN1 implements CCN {
	}
	
	@AnyJoinPointAnnotation 
	public class CCN2 {
	
	}
 	
 	public void handOut(CCN ccn) {
	...
	}
	public static void main(String[] args) { 
		CCN ccn1 = new CCN1();
		CCN ccn2 = new CCN2();
		handOut(ccn1); //不会被增强,该方法不匹配上述PointCut
		handOut(ccn2); // 会被增强
	}

@annotation (限制匹配带有指定注解连接点)

上面带@的注解全部作用在类上面,其实不是很方面。有没有一个注解是可以直接注解到自己想实现增强的方法上的呢?Spring给出了@annotation标志符实现这个功能

	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.METHOD)
	public @interface AnnotationPointCut {
	}

	

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

基于PointCut使用也就基于这几个标志符,当然他们直接可以通过逻辑运算符来进一步加强匹配的规则

在这里插入图片描述
真是场景的使用,读者们可以配合使用。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值