AspectJ一文说明白

2021-01-18 14:22:16 浏览数 (1)

Aspectj与Spring AOP比较参考:https://www.jianshu.com/p/872d3dbdc2ca

XML配置方式

代码语言:javascript复制
<aop:aspect>:定义切面, 包括通知和切点. 是一般的bean
代码语言:javascript复制

<bean id="sleepHelperAspect" class="com.ghs.aop.SleepHelperAspect"></bean>

<aop:aspectj-autoproxy/>
<aop:config>
<aop:pointcut expression="execution(* *.sleep(..))" id="sleepPointcut"/>
<aop:aspect ref="sleepHelperAspect">
<!--前置通知-->
<aop:before method="beforeSleep" pointcut-ref="sleepPointcut"/>
<!--后置通知-->
<aop:after method="afterSleep" pointcut-ref="sleepPointcut"/>
</aop:aspect>
</aop:config> 
代码语言:javascript复制
<aop:advisor>  定义通知器, 跟切面一样,也包括通知(advice)和切点(PointCut) . 通知必须实现Advice接口.
代码语言:javascript复制
<bean id="sleepHelper" class="com.noob.aop.SleepHelper"></bean>

<aop:aspectj-autoproxy/>
<aop:config>
   <aop:pointcut expression="execution(* *.sleep(..))" id="sleepPointcut"/>
   <aop:advisor advice-ref="sleepHelper" pointcut-ref="sleepPointcut"/>
</aop:config>

基本概念

  1. Advice(通知、切面):某个连接点所采用的处理逻辑,也就是向连接点注入的代码, AOP在特定的切入点上执行的增强处理。
  2. JointPoint(连接点):程序运行中的某个阶段点,比如方法的调用、异常的抛出等。
  3. Pointcut(切入点): JoinPoint的集合,是程序中需要注入Advice的位置的集合,指明Advice要在什么样的条件下才能被触发,在程序中主要体现为书写切入点表达式。
  4. Advisor(增强): PointCut 和 Advice的综合体,完整描述了一个advice将会在pointcut所定义的位置被触发。
  5. @Aspect(切面): 通常是一个类的注解,类中可以定义切入点和通知
  6. AOP Proxy:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类。

Pointcut

表示式(expression)和签名(signature)

代码语言:javascript复制
//Pointcut表示式
@Pointcut("execution(* com.savage.aop.MessageSender.*(..))")
//Point签名
private void log(){}

由下列方式来定义或者通过 &&、 ||、 !、 的方式进行组合:

  • execution:匹配 方法执行的连接点;
  • within: 目标对象target的类型是否和within中指定的类型匹配 。参数可指定包路径或具体的类全路径名。 匹配原则:target.getClass().equals(within表达式中指定的类型)
  • this(类型全限定名): 通过aop创建的代理对象的类型是否和this中指定的类型匹配;注意判断的目标是代理对象;this中使用的表达式必须是类型全限定名,不支持通配符。 匹配原则:this(x),则代理对象proxy满足下面条件时会匹配: x.getClass().isAssignableFrom(proxy.getClass());
  • target(类型全限定名): 判断目标对象的类型是否和指定的类型匹配;注意判断的目标是实际对象的类型;表达式必须是类型全限定名,不支持通配符。 匹配原则: target(x),则目标对象target满足下面条件时会匹配 x.getClass().isAssignableFrom(target.getClass());
  • args:匹配 当前执行的方法传入的参数为指定类型的执行方法;
  • @within:自定义注解标注在类上,该类的所有方法(不包含子类方法)执行aop方法
  • @target:匹配 当前目标对象类型的执行方法,其中目标对象类持有指定的注解;
  • @args:当前执行的方法传入的参数持有指定注解;
  • @annotation:匹配 执行方法持有指定注解;

格式

代码语言:javascript复制
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)   其中后面跟着“?”的是可选项

括号中各个pattern分别表示:

  • 修饰符匹配(modifier-pattern?)
  • 返回值匹配(ret-type-pattern): 可以为*表示任何返回值, 全路径的类名等
  • 类路径匹配(declaring-type-pattern?)
  • 方法名匹配(name-pattern):可以指定方法名 或者 *代表所有, set* 代表以set开头的所有方法
  • 参数匹配((param-pattern)):可以指定具体的参数类型。多个参数间用“,”隔开,各个参数也可以用"*" 来表示匹配任意类型的参数,".."表示零个或多个任意参数。 eg. (String)表示匹配一个String参数的方法;(*,String) 表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是String类型。
  • 异常类型匹配(throws-pattern?)

  • 任意公共方法的执行:execution(public * *(..))
  • 任何一个以“set”开始的方法的执行:execution(* set*(..))
  • AccountService 接口的任意方法的执行:execution(* com.xyz.service.AccountService.*(..))
  • 定义在service包里的任意方法的执行: execution(* com.xyz.service.*.*(..))
  • 定义在service包和所有子包里的任意类的任意方法的执行:execution(* com.xyz.service..*.*(..)) 第一个*表示匹配任意的方法返回值, ..(两个点)表示零个或多个,第一个..表示service包及其子包,第二个*表示所有类, 第三个*表示所有方法,第二个..表示方法的任意参数个数
  • 定义在pointcutexp包和所有子包里的JoinPointObjP2类的任意方法的执行:execution(* com.test.spring.aop.pointcutexp..JoinPointObjP2.*(..))")
  • pointcutexp包里的任意类: within(com.test.spring.aop.pointcutexp.*)
  • pointcutexp包和所有子包里的任意类:within(com.test.spring.aop.pointcutexp..*)
  • 实现了Intf接口的所有类,如果Intf不是接口,限定Intf单个类:this(com.test.spring.aop.pointcutexp.Intf) 当一个实现了接口的类被AOP的时候,用getBean方法必须cast为接口类型,不能为该类的类型
  • 带有@Transactional标注的所有类的任意方法:
    • @within(org.springframework.transaction.annotation.Transactional)
    • @target(org.springframework.transaction.annotation.Transactional)
  • 带有@Transactional标注的任意方法:@annotation(org.springframework.transaction.annotation.Transactional) @within和@target针对类的注解,@annotation是针对方法的注解
  • 参数带有@Transactional标注的方法:@args(org.springframework.transaction.annotation.Transactional)
  • 参数为String类型(运行是决定)的方法: args(String)

JoinPoint

  1. @Before: AspectJMethodBeforeAdvice标识一个前置增强方法.
  2. @After: AspectJAfterAdvicefinal增强,不管是抛出异常或者正常退出都会执行.
  3. @AfterReturning: AspectJAfterReturningAdvice后置增强,方法正常退出时执行.
  4. @AfterThrowing: AspectJAfterThrowingAdvice异常抛出增强.
  5. @Around: AspectJAroundAdvice环绕增强.

常用的方法:

  • Object[] getArgs:返回目标方法的参数
  • Signature getSignature():返回目标方法的签名信息。可获取方法名等
  • Object getTarget():返回被织入增强处理的目标对象
  • Object getThis():返回AOP框架为目标对象生成的代理对象

使用@Around处理时,需要将第一个JoinPoint参数定义为ProceedingJoinPoint类型才可使用方法proceed

因为在org.springframework.aop.aspectj.AbstractAspectJAdvice 里默认supportsProceedingJoinPoint()为false; 而AspectJAroundAdvice重写为true;

代码语言:javascript复制
Caused by: java.lang.IllegalArgumentException: ProceedingJoinPoint is only supported for around advice
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.maybeBindProceedingJoinPoint(AbstractAspectJAdvice.java:414) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.aop.aspectj.AbstractAspectJAdvice.calculateArgumentBindings(AbstractAspectJAdvice.java:388) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory.getAdvice(ReflectiveAspectJAdvisorFactory.java:294) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.aop.aspectj.annotation.InstantiationModelAwarePointcutAdvisorImpl.instantiateAdvice(InstantiationModelAwarePointcutAdvisorImpl.java:149) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.aop.aspectj.annotation.InstantiationModelAwarePointcutAdvisorImpl.<init>(InstantiationModelAwarePointcutAdvisorImpl.java:113) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory.getAdvisor(ReflectiveAspectJAdvisorFactory.java:198) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory.getAdvisors(ReflectiveAspectJAdvisorFactory.java:126) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.aop.aspectj.annotation.BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors(BeanFactoryAspectJAdvisorsBuilder.java:110) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator.findCandidateAdvisors(AnnotationAwareAspectJAutoProxyCreator.java:95) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator.shouldSkip(AspectJAwareAdvisorAutoProxyCreator.java:101) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessBeforeInstantiation(AbstractAutoProxyCreator.java:251) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInstantiation(AbstractAutowireCapableBeanFactory.java:1124) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.resolveBeforeInstantiation(AbstractAutowireCapableBeanFactory.java:1097) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:504) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    ... 15 common frames omitted

spring-core模块中: 提供支持cgliborg.springframework.cglib.proxy.MethodInterceptor是给 org.springframework.cglib.proxy.Enhancer使用的 org.springframework.cglib.proxy.Callback

代码语言:javascript复制
public interface MethodInterceptor extends Callback {
   Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}

spring-aop模块中:org.aopalliance.intercept.MethodInterceptor根本上是一个org.aopalliance.aop.Advice

代码语言:javascript复制
public interface MethodInterceptor extends Interceptor {
	Object invoke(MethodInvocation invocation) throws Throwable;
}

public interface Interceptor extends Advice {}

织入

代码语言:javascript复制
     @AfterReturning(
            pointcut="execution(* com.abc.service.*.access*(..)) && args(time, name)",
            returning="returnValue")
    public void invoke(Date time, Object returnValue, String name) {
        System.out.println("目标方法中的参数String = "   name);
        System.out.println("目标方法中的参数Date = "   time);
        System.out.println("目标方法的返回结果returnValue = "   returnValue);
    }

表达式中增加了args(time, name)部分,意味着可以在增强处理的签名方法(access方法)中定义"time"和"name"两个同名属性。这两个形参的类型由access方法同名参数类型指定。一旦指定了, 则这两个形参类型将用于限制该切入点只匹配第一个参数类型为Date,第二个参数类型为String的方法(方法参数个数和类型若有不同均不匹配); access方法只需要满足"time", "name"参数的顺序和pointcut中args(time, name)的顺序相同即可,"returnValue"位置顺序无所谓。

eg.

代码语言:javascript复制
//将被access方法匹配
public String accessAdvice(Date d, String n) {
    System.out.println("方法:accessAdvice");
    return "aa";
}

执行顺序

代码语言:javascript复制
package com.noob.controller.Interceptor;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AdviceInterceptor {
	@Pointcut(value = "execution(public String com.noob.controller.BService.testAdvice(..))")
	public void pointcut() {}

	@Before("pointcut()")
	public void before(JoinPoint point) {
		System.out.println("before");
	}

	@Around("pointcut()")
	public Object around(ProceedingJoinPoint point) throws Throwable {
		System.out.println("around begin");
		try {
			Object proceed = point.proceed();
			System.out.println("around after");
			return proceed;
		} catch (Throwable e) {
			System.out.println("around after exception");
			throw e;
		}
	}

	@After("pointcut()")
	public void after() throws Throwable {
		System.out.println("after");
	}

	@AfterReturning("pointcut()")
	public void afterReturning() throws Throwable {
		System.out.println("afterReturning");
	}

	@AfterThrowing("pointcut()")
	public void afterThrowing() {
		System.out.println("afterThrowing");
	}
}
---
@Component
public class BService {

	public String testAdvice() {
		System.out.println("目标方法testAdvice");
		throw new RuntimeException("fail");
		// return "testAdvice";
	}
}

正常情况执行结果:

代码语言:javascript复制
around begin
before
目标方法testAdvice
around after
after
afterReturning

有异常情况下:

代码语言:javascript复制
around begin
before
目标方法testAdvice
around after exception
after
afterThrowing

测试过程中发现,debug模式下可知JoinPoint的实际类型是org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint(before、around 都是该实际类型)

但若代码直接写定该类型,启动报错

代码语言:javascript复制
Caused by: java.lang.IllegalArgumentException: error at ::0 formal unbound in pointcut 
    at org.aspectj.weaver.tools.PointcutParser.parsePointcutExpression(PointcutParser.java:319) ~[aspectjweaver-1.9.4.jar:na]
    at org.springframework.aop.aspectj.AspectJExpressionPointcut.buildPointcutExpression(AspectJExpressionPointcut.java:227) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.aop.aspectj.AspectJExpressionPointcut.obtainPointcutExpression(AspectJExpressionPointcut.java:198) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.aop.aspectj.AspectJExpressionPointcut.getClassFilter(AspectJExpressionPointcut.java:177) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.aop.support.AopUtils.canApply(AopUtils.java:225) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.aop.support.AopUtils.canApply(AopUtils.java:288) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.aop.support.AopUtils.findAdvisorsThatCanApply(AopUtils.java:320) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.findAdvisorsThatCanApply(AbstractAdvisorAutoProxyCreator.java:126) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.findEligibleAdvisors(AbstractAdvisorAutoProxyCreator.java:95) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean(AbstractAdvisorAutoProxyCreator.java:76) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:347) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:299) ~[spring-aop-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:429) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1782) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]

因为, 执行到ReflectiveAspectJAdvisorFactory.getAdvisors -> ReflectiveAspectJAdvisorFactory.getAdvice -> AbstractAspectJAdvice.calculateArgumentBindings()时:对JoinPoint.classProceedingJoinPoint.class是不用额外处理参数绑定。

所以MethodInvocationProceedingJoinPoint被当作额外的参数。

后续先执行进入org.aspectj.weaver.tools.PointcutParser.parsePointcutExpression -> PointcutParser.resolvePointcutExpression:

PointcutParser.buildResolutionScope创建参数类型绑定关系。

接着执行进入org.aspectj.weaver.patterns.Pointcut.resolve:

首先 Pointcut.resolveBindings来根据实际情况解析该绑定关系;接着执行校验方法Bindings.checkAllBound因真实绑定为空,判定失败后异常

同一个方法被多个Aspect类拦截

优先级高的切面类里的增强处理 优先于 优先级低的切面类。

在“进入”连接点时,最高优先级的增强处理将先被织入(eg. 给定的两个不同切面类Before增强处理中,优先级高的那个会先执行);

在“退出”连接点时,最高优先级的增强处理会最后被织入(eg. 给定的两个不同切面类After增强处理中,优先级高的那个会后执行)。

eg. 优先级为1的切面类Bean1包含了@Before,优先级为2的切面类Bean2包含了@Around,虽然@Around优先级高于@Before,但由于Bean1的优先级高于Bean2的优先级,因此Bean1中的@Before先被织入。

Spring提供了如下两种解决方案指定不同切面类里的增强处理的优先级:

  1. 让切面类实现org.springframework.core.Ordered接口:实现该接口的int getOrder()方法,该方法返回值越小,优先级越高
  2. 直接使用@Order注解来修饰一个切面类:使用这个注解时可以配置一个int类型的value属性,该属性值越小,优先级越高

同一个切面类里的两个相同类型的增强处理在同一个连接点被织入时,Spring AOP将以随机的顺序来织入这两个增强处理,没有办法指定它们的织入顺序。即使给这两个 advice 添加了 @Order 这个注解,也不行!

喜欢,在看

0 人点赞