7.7 KiB
7.7 KiB
Spring AOP
-
Spring AOP的核心概念
- Aspect:切面,一个模块化的考虑
- Joint Point:连接点,程序执行时的一个时间点,通常是方法的执行
- Advice:当切面在一个切入点执行多做时,执行的动作被称之为Advice,Advice有不同的类型:before、after、around
- Pointcut:切入点,advice通常运行在满足pointcut的join point上,pointcut表达式与join point相关联,Spring中默认使用AspectJ切入点表达式
- Introduction:在类中声明新的方法、域变量甚至是接口实现
- linking:将应用类型或对象和切面链接起来
-
Spring AOP的类型
- before:在连接点之前运行,但是无法阻止后续连接点的执行
- after returning:在连接点正常返回之后进行
- after throwing:在链接点抛出异常正常退出之后进行
- after finally:上两种的结合,不管连接点是正常退出还是抛出异常退出,都会在其之后执行
- around:around可以自定义连接点之前和之后的执行内容,其也能够选择时候执行连接点的方法
-
Spring AOP的特点
- 区别于AspectJ AOP框架,Spring AOP框架是基于代理来实现的
- 对于实现了接口的类,Spring AOP通常是通过JDK动态代理来实现的,对于没有实现接口的类,Spring AOP是通过cglib来实现的
- 可以强制Spring AOP使用cglib,在如下场景:
- 如果想要advise类中方法,而该方法没有在接口中定义
- 如果想要将代理对象传递给一个具有特定类型的方法作为参数
-
Spring AOP的AspectJ注解支持
- Spring AOP支持AspectJ注解,Spring AOP可以解释和AspectJ 5相同的注解,通过使用AspectJ提供的包来进行切入点解析和匹配
- 但是,即使使用了AspectJ注解,AOP在运行时仍然是纯粹的Spring AOP,项目不需要引入AspectJ的编译器和weaver
- Spring AOP对AspectJ注解支持的开启
- 通过@EnableAspectJAutoProxy注解,会自动的为满足切入点匹配的连接点bean对象创建移动代理对象
@Configuration @EnableAspectJAutoProxy class AspectJConfiguration { // ... }
-
声明Spring AOP切面
- 在容器中,任何bean对象,如其类型具有@AspectJ注解,将会被自动探知到并且用来配置spring aop
- 在Spring AOP中,aspect其自身是无法作为其他aspect的目标对象的。被标记为@Aspect的类,不仅标明其为aspect,并且将其从自动代理中排除
- 如果为某个bean对象配置了切面,那么在后续创建该bean对象时,实际上是创建该bean对象的代理对象
@Component // 将该类型声明为bean对象 @Aspect // 声明切面 public class ProxyAspect { } -
声明Spring AOP切入点
- 由于Spring AOP仅仅支持方法的连接点,故而可以将切入点看做对bean对象方法的匹配
- Join Point expression的种类:
- execution:匹配目标方法的执行,可以在括号中接收一个函数签名,包含返回类型、函数名和函数参数类型
// 被@JointPoint注解标注的方法必须具有void的返回类型 @JoinPoint("execution(* Point.*(..))") void methodInjected() { }- within:匹配声明在某一特定类中的方法
@JoinPoint("within(Point)")- this:匹配生成的代理对象为该类型的一个实例
- target:匹配目标对象为该类型的一个实例
- args:匹配特定参数
- @args:传递参数的类型具有指定的注解
- @target:运行时该对象的类具有指定的注解
- @within:运行时执行的方法,其方法定义在具有指定注解的类中(可以是继承父类的方法,父类指定了注解
- @annotation:执行的方法具有指定注解
- Spring AOP同样支持将JoinPoint匹配为具有特定name的Spring bean对象
@JoinPoint("bean(nameA) || bean(nameB))")
-
Spring AOP中的Advice
- Advice和Pointcut Expresion相关联,主要可以分为before、after、around等种类
- Before:
@Before("execution(* Point.*(..))") public void doSomething() { }- AfterReturning:
// AfterReturning支持获取切入点执行后返回的值 @AfterReturning( pointcut="execution(* Point.*(..))", returning="retVal") public void doSomething(int retVal) { }- AfterThrowing:
@AfterThrowing( pointcut="execution(* Point.*())", throwing="ex" ) public void doSomething(Throwable ex) { }- After:After不管是切入点正常返回还是抛出异常,都会执行,类似于finally
@After("execution(* Point.*())") public void doSomething() { }- Around:其方法必须会一个Oject类型的返回值,并且方法的第一个参数类型是ProceedingJoinPoint
@Around("execution(* Point.*())") public Object doSomething(ProceedingJoinPoint pjp) { return isCacheExisted()?returnFromCache():pjp.proceed(); } -
Spring AOP中Advice方法对JoinPoint的访问
- 任何advice方法,都可以声明声明其第一个参数为JoinPoint类型。@Around标注的adivce方法其第一个参数的类型必须为ProceedingJoinPoint类型,该类型为JoinPoint的子类型
- JoinPoint接口具有如下方法:
- getArgs:返回方法参数
- getThis:返回代理对象
- getTarget:返回目标对象
- getSignature:返回函数的签名
- toString:返回该advice方法的描述信息
-
Advice方法通过参数来获取传递给底层方法的参数
- 在pointcut表达式的args中,如果用advice方法中的参数名来代替参数类型,那么该类型的参数值会被传递给该参数
@Before("execution(* Point.*(..) && args(position,..))") public void adviceMethod(Position position) { }- 或者,可以通过如下方式,先通过一个Pointcut获取参数,在在另一个方法中获取named pointcut已获取的参数
// 此时,adviceMethodTwo同样能够获取Position参数 @Pointcut("execution(* Point.*(..)) && args(position,..)") public void adviceMethodOne(Position position) { } @Before("adviceMethodOne(position)") public void adviceMethodTwo(Position position) { }- Spring AOP可以通过如下方式来约束泛型的参数
@Before("execution(* GenericsInterface+.method(*) && args(param))") public void adviceMethod(DesiredType param) { } -
通过Spring AOP对参数进行预处理
@Around("execution(* Point.area(*) && args(width,height))") public double caculateInCM(ProceedingJoinPoint jp,double width,double height) { width*=100; height*=100; return jp.proceed(width,height); } -
Spring AOP中多个advice对应到同一个Pointcut
- 如果多个advice都具有相同的pointcut,那么多个advice之间的执行顺序是未定义的。可以为Aspect类实现Ordered接口,或者添加@Order标记来定义该advice的执行优先级,那么具有具有较小order值的方法将会优先被执行
-
Spring AOP Introduction
- 在Spring AOP中,可以通过Introduction来声明一个对象继承了某接口,并且为被代理的对象提供被继承接口的实现
- 可以通过@DeclareParent注解为指定对象添加接口并且指明该接口默认的实现类,完成后可以直接将生成的代理对象复制给接口变量
@Aspect public class MyAspect { @DeclareParent(value="cc.rikakonatsumi.interfaces.*+",defaultImpl=DefaultImpl.class) private static MyInterface myInterface; // 之后,可以直接通过this(ref)在pointcut表达式中获取服务对象,也可以通过getBean方法获取容器中的对象 }