【修炼内功】[spring-framework] [5] Spring AOP 是如何代理的

本文已收录【修炼内功】跃迁之路

spring-framework.jpg

林中小舍.png

在之前的文章中介绍了Spring的IoC(Resource | BeanDefinitionReader | BeanFactory | ApplicationContext),如果说前者是Spring的基石,则本篇要介绍的AOP则可以称作是Spring的点睛之笔,它在整个Spring生态中扮演着重要的角色
Aspect-oriented Programming (AOP) complements Object-oriented Programming (OOP) by providing another way of thinking about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects enable the modularization of concerns that cut across multiple types and objects.

前言

代理模式

代理可以看作是对调用目标的一个包装,目标代码的调用不是直接发生的而是通过代理完成,以此来让调用者与实现者之间解耦,从而(在无侵入的情况下)在目标代码之外实现一些额外(增强)功能

实现代理的方法有很多种

  • 可直接编码增加一个代理层实现目标代码的代理调用,此过程发生在编译阶段,称之为静态代理(可参见java-desion-patterns-proxy
  • 可借助jdk-proxy、cglib等技术生成代理类,并通过反射机制对目标代码进行代理调用,此过程发生在运行阶段,称之为动态代理(可参见commons-proxy中的各工具)
  • 也可借助javassist、asm等字节码增强技术直接修改(增强)目标字节码,这种方式类似于编程语言中的内联(inline),严格来讲并不属于编程模式的范畴

AOP

维基百科-面向切面的程序设计

Aspect-Oriented Programming,面向切面的程序设计,是计算机科学中的一种程序设计思想,旨在将横切关注点与业务主体进行进一步分离,以提高程序代码的模块化程度。通过在现有代码基础上增加额外的通知(Advice)机制,能够对被声明为“切点(Pointcut)”的代码块进行统一管理与装饰。

从AOP的定义上来看,其与代理模式十分相似,那可以说AOP就是代理模式的一种实现么?并非如此,AOP相比于代理模式表现的更为灵活也更细腻,典型的框架如 AspectJ

Spring AOP

Spring AOP 将面向切面的设计思想引入到了Spring IoC中,但Spring AOP并不是AOP的完备实现,其设计目标在于:

  • Spring AOP的作用范围仅限于Spring IoC中,更为通俗的讲,Spring AOP的切面只能作用在Spring IoC所管理的Bean中

    Rather, the aim is to provide a close integration between AOP implementation and Spring IoC, to help solve common problems in enterprise applications.
  • Spring AOP仅支持方法级的切面,其更像是给目标(切面)方法定义了一个拦截器(method-interceptor)

    Spring AOP currently supports only method execution join points (advising the execution of methods on Spring beans).

Spring AOP具体的能力及设计目标见 Spring AOP Capabilities And Goals

Spring AOP借助了AspectJ的很多能力(注解、切面匹配、拦截器、等),但其织入(Weaving)过程并未使用AspectJ,而是使用了 JDK Proxy/CGLIB 创建代理类,通过反射的方式实现目标方法的调用,Spring AOP及AspectJ在功能上的区别比较见 Comparing Spring AOP and AspectJ

Spring框架并未直接引入CGLIB,而是自己维护(repackaging)了一套源码(包括asm在内),可参考spring-core内的org.springframework.cglib包及org.springframework.asm

相比于AspectJ之类的AOP框架而言,Spring AOP更加简洁,但Spring AOP是如何实现的?这里不妨先留几个疑问,带着问题去理解Spring AOP的实现逻辑

  1. Spring AOP是在哪个阶段生成代理类的?
  2. Spring AOP是如何找到需要代理的方法的?
  3. Spring AOP是如何(在代理类中)封装代理方法的?
  4. 如果一个被代理方法匹配多个代理规则,不同的代理规则是如何依次执行的(invoke chain)?

Spring AOP 基础概念

在深入Spring AOP的实现原理之前,先熟悉几个AOP的基本概念 (AOP Concepts)

  • Pointcut

    切入点,在哪些类的哪些方法上进行切入(拦截),其通常表现为一个表达式(AspectJExpressionPointcut),通过该表达式匹配哪些类(ClassFilter)及哪些方法(MethodMatcher)需要被拦截代理

  • JoinPoint

    连接点,由于Spring AOP仅支持方法,在Spring AOP中可以理解为,通过Pointcut匹配到的需要被拦截的方法

  • Advice

    通知,拦截到连接点后,需要代理执行的代码逻辑,其一共分为 AroundBeforeAfterAfterReturningAfterThrowing 五类

  • Aspect

    切面,即 Pointcut + Advice,在哪些类的哪些方法上,代理执行哪些逻辑

  • Weaving

    织入,将切面应用到目标对象,生成代理对象的过程

  • Target Object

    目标(被代理)类对象,连接点所在的类对象

  • Proxy Object

    生成的代理对象

  • Introduction

    引入,在不修改目标类代码的前提下,可在运行期动态的增加一些方法(或属性)

Spring AOP 注册

Spring AOP的注册有多种方式,主要以注解及xml配置为主,本篇着重介绍注解方式,xml配置的方式原理相通

为了启用Spring AOP,一般都会使用注解@EnableAspectJAutoProxy,或者xml配置<aop:aspectj-autoproxy>

@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
    boolean proxyTargetClass() default false;
    boolean exposeProxy() default false;
}

proxyTargetClassexposeProxy参数的作用参见JavaBean Properties,重点在AspectJAutoProxyRegistrar(@Import注解的使用在以后的章节中介绍)实现了ImportBeanDefinitionRegistrar接口(其作用在于动态注册Bean)

跟踪源码(AopConfigUtils#registerAspectJAnnotationAutoProxyCreatorIfNecessary)会发现@EnableAspectJAutoProxy注解通过AspectJAutoProxyRegistrar注册器注册了AnnotationAwareAspectJAutoProxyCreator,该Creator实现了BeanPostProcessor

Bean是如何被创建的一文中介绍过,在Bean实例化并初始化完成后,会调用BeanPostProcessor#postProcessAfterInitialization,该方法可以返回一个新的实例对象,AnnotationAwareAspectJAutoProxyCreator对目标对象的代理创建过程,可能就发生在此步骤

// org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        // ... 略去
        // 切面匹配,如果需要则生成代理bean
        return wrapIfNecessary(bean, beanName, cacheKey);
    }
    return bean;
}

看到wrapIfNecessary是否有豁然开朗的感觉?

所以,Spring AOP对目标对象的代理创建过程发生在BeanPostProcessor#postProcessAfterInitialization阶段,即目标Bean实例化且初始化完成之后

AOPCreate

AnnotationAwareAspectJAutoProxyCreator实际直接实现了InstantiationAwareBeanPostProcessor接口,在目标对象创建之前便会有一个短路的操作,可能直接在该步骤生成代理对象而不需要目标对象的实例化,具体逻辑可以参考AbstractAutoProxyCreator#postProcessBeforeInstantiation

Spring AOP 切面的匹配

查看wrapIfNecessary的逻辑,整体上并不复杂,找到该bean所有匹配的切面Advisors,并根据所匹配的切面创建代理bean

wrapIfNecessary

首选关心的是,如何找到所有匹配的Advisors

AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean

AbstractAdvisorAutoProxyCreator#findEligibleAdvisors

findEligibleAdvisors

获取所有已注册的Advisors

AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors

Advisor的注册有两大途径,直接注册Advisor类型的Bean 或者 注册被@Aspect修饰的Bean

findCandidateAdvisors

找到所有Advisor类型的Beans

AbstractAdvisorAutoProxyCreator#findCandidateAdvisors

BeanFactoryAdvisorRetrievalHelper#findAdvisorBeans

这一步骤其实并不复杂,在BeanFactory中找到所有注册为Advisor类型的Bean并提前初始化

findAdvisorBeans

查看Advisor的源码十分简单,只有一个方法会比较感兴趣

public interface Advisor {
    Advice getAdvice();
}

而Advice则显得更加神秘,没有任何方法接口定义

public interface Advice {}

留个悬念,客官且看

Q: Advisor是什么?Advice的实现类都有哪些?如何直接注册Advisor的Bean?通过注册Advisor Bean来实现AOP的场景有哪些?

找到所有@Aspect修饰的Beans并解析

BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors

buildAspectJAdvisors

Spring将所有注册的bean-names拿出遍历,过滤出被@Aspect注解修饰的Bean,通过@Apsect注解及Aspect-Bean信息生成BeanFactoryAspectInstanceFactory,用于后续Advisor的解析(ReflectiveAspectJAdvisorFactory#getAdvisors

getAdvisors

从流程图可以看出,Advisor的解析主要来自方法及属性两部分,方法主要用来解析@Around@Before@After@AfterReturing@AfterThrowing,属性主要用来解析@DeclareParents

方法解析

ReflectiveAspectJAdvisorFactory#getAdvisor

getAdvisor

Spring会查找被@Around@Before@After@AfterReturing@AfterThrowing(如果一个方法同时被多个注解修饰,按顺序只会匹配第一个被找到的注解)修饰的方法,将注解的信息封装为AspectJExpressionPointcut,将方法的内部逻辑封装为不同类型的xxxAdvice,并最终将上述的PointcutAdvice组合封装为InstantiationModelAwarePointcutAdvisorImpl提供出去

这里出现了开篇提到的几个概念 PointcutAdvice

Pointcut

Pointcut

Pointcut的实现非常多,深入之前先浏览下Pointcut的接口定义

public interface Pointcut {
    ClassFilter getClassFilter();
    MethodMatcher getMethodMatcher();
}

不难猜想,Spring AOP就是通过这两个方法来判断哪些类的哪些方法需要被代理,反过来讲通过这两个方法判断当前Bean的各个方法有没有以及匹配上了哪些Advice

众多的Pointcut实现中,重点介绍其中的几个

  • TruePointcut

    该Pointcut的实现非常简单,无论入参是什么,ClassFilter及MethodMatcher的matches方法总会返回true

  • AnnotationMatchingPointcut

    该Pointcut只关注目标类上的注解修饰,ClassFilter#matches用来判断目标类上是否存在指定的注解,MethodMatcher#matches总会返回true

  • AspectJExpressionPointcut

    该Pointcut是本篇的重点,通常而言无论使用注解方式还是xml方式配置AOP,通常在声明Pointcut的时候都会定义一段表达式用来匹配我们想要拦截/代理的方法,这样的声明方式Spring AOP均会将其封装为AspectJExpressionPointcut,其内部的匹配逻辑比较复杂,在这里不多展开讨论,感兴趣的可以查看 AspectJExpressionPointcut#matches

    既然是表达式,便比较关心Spring AOP都支持哪些表达式,从源码中不难看出

    public class AspectJExpressionPointcut extends AbstractExpressionPointcut
            implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {
        private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<>();
        static {
            // execution()
            SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
            // args()
            SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
            // reference()
            SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
            // this()
            SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
            // target()
            SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
            // within()
            SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
            // @annotation()
            SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
            // @within
            SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
            // @arges()
            SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
            // @target()
            SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
        }
    }

    具体的含义参考 Supported Pointcut Designators,用法参考 AOP Pointcut Examples,使用与、或、非运算参考 Combining Pointcut Expressions

  • ComposablePointcut

    看到composable应该就能猜到这是一个复合的Pointcut,可以将多个Pointcut组合到一起进行unionintersection运算

Advice

Advice的定义比较让人摸不到头脑,并没有声明任何方法

/**
 * Tag interface for Advice. Implementations can be any type
 * of advice, such as Interceptors.
 */
public interface Advice {}

但是上文已经讲了,Spring AOP会根据不同的注解生成不同的Advice实现

注解 Advice实现
@Around AspectJAroundAdvice
@Before AspectJMethodBeforeAdvice
@After AspectJAfterAdvice
@AfterReturning AspectJAfterReturningAdvice
@AfterThrowing AspectJAfterThrowingAdvice

Spring AOP在生成以上任意一种Advice实现时,都会将

  • candidateAdviceMethod

    以上各注解修饰的advice方法

  • expressionPointcut

    上述生成的AspectJExpressionPointcut

  • aspectInstanceFactory
    上文讲的通过@Apsect注解及Aspect-Bean信息生成BeanFactoryAspectInstanceFactory作为构造参数传入

这是之后定位拦截的方法、生成代理类、执行代理逻辑必须的内容

除了AspectJMethodBeforeAdviceAspectJAfterReturningAdvice之外,其他三个Advice实现均实现了MethodInterceptor接口

public interface MethodInterceptor extends Interceptor {
    Object invoke(MethodInvocation invocation) throws Throwable;
}

这便是拦截/代理的POINT,查看各Advice对invoke方法的实现逻辑

// AspectJAroundAdvice
public Object invoke(MethodInvocation mi) throws Throwable {
    ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
    // 将目标方法封装为ProceedingJoinPoint
    ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);
    JoinPointMatch jpm = getJoinPointMatch(pmi);
    // 将目标方法的ProceedingJoinPoint传入advice方法进行调用
    return invokeAdviceMethod(pjp, jpm, null, null);
}
// AspectJAfterAdvice
public Object invoke(MethodInvocation mi) throws Throwable {
        try {
            // 调用目标方法
            return mi.proceed();
        }
        finally {
            // 调用advice方法
            invokeAdviceMethod(getJoinPointMatch(), null, null);
        }
    }
// AspectJAfterThrowingAdvice
public Object invoke(MethodInvocation mi) throws Throwable {
    try {
        // 调用目标方法
        return mi.proceed();
    }
    catch (Throwable ex) {
        if (shouldInvokeOnThrowing(ex)) {
            // 调用advice方法
            invokeAdviceMethod(getJoinPointMatch(), null, ex);
        }
        throw ex;
    }
}

到这里,似乎对Spring AOP的流程已经有些眉目了,那AspectJMethodBeforeAdviceAspectJAfterReturningAdvice是如何执行的?

其又分别对应另外两个AdviceInterceptor(无辜脸~)

Advice实现 对应的MethodInterceptor实现
AspectJMethodBeforeAdvice MethodBeforeAdviceInterceptor
AspectJAfterReturningAdvice AfterReturningAdviceInterceptor

再来看这两种MethodInterceptor的invoke实现

// MethodBeforeAdviceInterceptor
public Object invoke(MethodInvocation mi) throws Throwable {
    // 调用advice方法
    this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
    // 调用目标方法
    return mi.proceed();
}
// AfterReturningAdviceInterceptor
public Object invoke(MethodInvocation mi) throws Throwable {
    // 调用目标方法
    Object retVal = mi.proceed();
    // 调用advice方法
    this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
    return retVal;
}
所以,你有看懂@After和@AfterReturning的区别了么?

Advice

Q: MethodBeforeAdviceInterceptor及AfterReturningAdviceInterceptor是在什么时候封装的?
Advisor

没有特别的逻辑在里边,封装了上文提到的Pointcut实现及Advice实现

属性解析

ReflectiveAspectJAdvisorFactory#getDeclareParentsAdvisor

getDeclareParentsAdvisor

主要用于对@DeclareParents修饰的属性进行解析,用来支持Introduction

需要增强的接口为@DeclareParents修饰的属性类型,增强的接口实现为@DeclareParents中的defaultImpl参数

匹配符合条件的Advisors

AbstractAdvisorAutoProxyCreator#findAdvisorsThatCanApply

AopUtils#findAdvisorsThatCanApply

通过上面的过程(每一个节点都是存在缓存的,以上的逻辑只在首次触发)已经拿到了容器中所有已注册的Advisors,现在的流程还处在BeanPostProcessor#postProcessAfterInitialization中,只需要使用每一个Advisor中Pointcut的ClassFilter及MethodMatcher去匹配当前的目标bean便可过滤出需要应用到当前Bean上的Advisor

这部分逻辑比较简单,以文字描述其过程

  1. 如果是IntroductionAdvisor,则取出ClassFilter对当前Bean进行match
  2. 如果是PointcutAdvisor,则取出ClassFilter对当前Bean进行match,通过后依次遍历当前Bean-Class中的每一个方法,使用MethodMatcher进行match,只要有一个方法匹配上则通过

添加额外的Advisor

AspectJAwareAdvisorAutoProxyCreator#extendAdvisors

AspectJProxyUtils#makeAdvisorChainAspectJCapableIfNecessary

如果当前的目标Bean存在匹配上的Advisors,则会在在Advisors列表中添加一个特殊的Advisor - ExposeInvocationInterceptor.ADVISOR

该Advisor的实现为DefaultPointcutAdvisor,内部Pointcut为TruePointcut(匹配任何类的任何方法),内部Advice为ExposeInvocationInterceptor,并且Order是最高的

ExposeInvocationInterceptor用于暴露当前线程正在执行的MethodInvocation

对Advisors进行排序

AspectJAwareAdvisorAutoProxyCreator#sortAdvisors

Advice会继承Aspect-Bean的Order(通过@Order注解,或实现Ordered或PriorityOrdered),对Advisors的排序则依赖以上(PriorityOrdered优先于Ordered及@Order)

所以,这里有一个问题,如果在一个Aspect-Bean中定义多个Advices,则同一个Aspect-Bean中Advices的顺序是没有办法定义/保障的(官方解释 Advice Ordering

Spring AOP 织入

AbstractAutoProxyCreator#createProxy

createProxy

Spring AOP会通过proxyTargetClass参数的设置及实际Bean Class的情况判断使用Cglib还是Jdk Proxy(这里默认您对Cglib及Jdk Proxy的使用有一定的了解)

对Cglib来讲,关键在于设置Enhancer的Callback,逻辑可以参考CglibAopProxy#getCallbacks

对JDK Proxy来讲,关键在于定义InvocationHandler,逻辑可以参考JdkDynamicAopProxy#invoke

如果一个Bean中的方法匹配上多个Advice,那多个Advice的invoke方法是如何串行执行的?以JdkDynamicAopProxy中的逻辑为例,有两处代码会比较有意思

// org.springframework.aop.framework.JdkDynamicAopProxy#invoke

// 找到当前方法匹配到Advices
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// ...
// 将以上Advices封装为ReflectiveMethodInvocation
MethodInvocation invocation =
        new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// 触发整条链
retVal = invocation.proceed();

这里类似于Servlet应用中的FilterChain,将advice chain一步步代入,以剥洋葱的方式调用

上文提到的所有Advice实现均会实现MethodInterceptor,内部invoke方法的入参类型为MethodInvocation,注意,ReflectiveMethodInvocation同样实现了MethodInterceptor,简单描述ReflectiveMethodInvocation#proceed的流程如下

ReflectiveMethodInvocation.proceed

关键在于各Advice的实现类中执行invoke方法时,调用的MethodInvocation.proceed既是ReflectiveMethodInvocation#proceed本身

如果存在顺序的三个AroundAdvice、BeforeAdvice、AfterAdvice匹配同一个方法,整个调用过程将会是

ReflectiveMethodInvocation.demo

Q: Spring AOP还提供了Listener支持(AdvisedSupportListener),如何使用?

小结

  1. 注册切面可以通过@Aspcet注解、xml配置或者Advisor类型的Bean
  2. Spring AOP会寻找@Aspect修饰的Bean,并在其中寻找@Around@Before@After@AfterReturing@AfterThrowing修饰的方法及@DeclareParents修饰的属性
  3. Aspect Bean中符合条件的方法/属性会被封装为Advisor(Pointcut + Advice)
  4. Spring AOP通过BeanPostProcessor(AnnotationAwareAspectJAutoProxyCreator)实现目标Bean的代理创建
  5. Spring AOP通过Pointcut中的ClassFilterMethodMatcher匹配目标Bean需要应用的Advisor
  6. Spring AOP通过proxyTargetClass参数的设置及实际Bean Class的情况判断使用Cglib还是Jdk Proxy
  7. 如果一个Bean中的方法匹配上多个Advice,那多个Advice的invoke方法通过ReflectiveMethodInvocation进行链式调用

Spring AOP 注解使用归纳

@Aspect

@Aspect修饰的类必须同时注册为Bean

@Aspect
@Component
public class CatAspectJSupport { /* ... */}

@Order

拦截/切面/代理的优先级可以通过@OrderOrderedPriorityOrdered控制

@Aspect
@Component
@Order(2)
public class CatAspectJSupport { /* ... */}
@Aspect
@Component
public class CatAspectJSupport implements Ordered /** PriorityOrdered **/ {
    @Override
    public int getOrder() { return 2; }
}

@Point

Pointcut可以通过@Pointcut注解声明,也可以直接通过@Around@Before@After@AfterReturing@AfterThrowing注解声明

通过@Pointcut注解声明可以重复使用,通过Advice注解可以将更多的参数引入Advice函数

@Pointcut("execution(* com.manerfan.springdemo.Cat.barkVoice(..))")
public void barkVoiceMethod() {}

@Around("barkVoiceMethod()")
public Object statistics(ProceedingJoinPoint jp) { /* .. */}

@Before("barkVoiceMethod())")
public void beforeBark(JoinPoint jp) { /* .. */}
@Around("@annotation(statistics)")
public Object statistics(ProceedingJoinPoint jp, Statistics statistics /* 直接将目标方法上的注解对象传入 */)  { /* .. */}

execution

For matching method execution join points.

使用强大的表达式拦截方法

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
// 所有包中所有类的所有公共方法
execution(public * *(..))
// com.manerfan.service.AccountServiceImpl类中的所有方法
execution(* com.manerfan.service.AccountServiceImpl.*(..))
// com.manerfan.service包中(包含子包)所有类中返回Long类型的方法
execution(java.lang.Long com.manerfan.service..*.*(..))
// com.manerfan.service.AccountServiceImpl类中所有两个参数且第二个参数是String类型的方法
execution(* com.manerfan.service.AccountServiceImpl.*(,java.lang.String))
注: 全类路径需要是被代理的类路径,非接口类路径

within

Limits matching to join points within certain types (the execution of a method declared within a matching type when using Spring AOP).

可以看做是execution的子集,用于匹配被拦截方法所在的类

// com.manerfan.service包下所有类中所有方法
within(com.manerfan.service.*)
// com.manerfan.service包中(包含子包)所有类中所有方法
within(com.manerfan.service..*)
// com.manerfan.service.AccountServiceImpl类中所有方法
within(com.manerfan.service.AccountServiceImpl)
注: 全类路径需要是被代理的类路径,非接口类路径

this

Limits matching to join points (the execution of methods when using Spring AOP) where the bean reference (Spring AOP proxy) is an instance of the given type.

用于匹配生成的代理对象的类型

// 代理对象的类型为com.manerfan.service.DogImpl
this(com.manerfan.service.DogImpl)
// 代理对象的类型为com.manerfan.service.Animal接口的所有实现
this(com.manerfan.service.Animal)
注: 只能使用完整类路径,不可使用通配符,可以使用实现类全路径,也可以使用接口全路径

初此之外,还可以使用参数匹配

// 匹配所有代理对象实现了Animal接口的类
@Before("this(animal)")
public void animalBefore(JoinPoint jp, Animal animal /* 这里代入的是生成的代理对象 */) { /* ... */ }

this还有一个用途便是应用到Introduction

// 为com.manerfan.service包下的所有类生成代理对象的时候默认实现Poultry接口,实现类是DuckImpl
@DeclareParents(value = "com.manerfan.service.*", defaultImpl = DuckImpl.class)
public Poultry poultry;

// 匹配所有代理对象实现了Poultry接口的类
@Before("this(poultry)")
public void animalBefore(JoinPoint jp, Poultry poultry /* 这里代入的是生成的代理对象 */) { /* ... */ }

target

Limits matching to join points (the execution of methods when using Spring AOP) where the target object (application object being proxied) is an instance of the given type.

this类似,但不同的是,target用于匹配目标Bean对象的类型

// 目标对象的类型为com.manerfan.service.DogImpl
target(com.manerfan.service.DogImpl)
// 代理对象的类型为com.manerfan.service.Animal接口的所有实现
target(com.manerfan.service.Animal)
注: 只能使用完整类路径,不可使用通配符,可以使用实现类全路径,也可以使用接口全路径

初此之外,还可以使用参数匹配

// 匹配所有目标对象实现了Animal接口的类
@Before("target(animal)")
public void animalBefore(JoinPoint jp, Animal animal /* 这里代入的是目标Bean对象 */) { /* ... */ }

args

Limits matching to join points (the execution of methods when using Spring AOP) where the arguments are instances of the given types.

用于匹配方法参数

// 匹配无参的函数
args()
// 匹配只有一个参数,且类型为String的函数
args(java.lang.String)
// 匹配任意参数的函数
args(..)
// 匹配任意参数且第一个参数为String的函数
args(java.lang.String,..)
// 匹配任意参数且最后一个参数为String的函数
args(..,java.lang.String)

初此之外,还可以使用参数匹配

// 匹配com.manerfan包下所有第一个参数是int类型的方法
@Before("within(com.manerfan..*) && args(index,...)")
public void animalBefore(JoinPoint jp, int index /* 这里代入目标方法的参数值 */) { /* ... */ }

@target

Limits matching to join points (the execution of methods when using Spring AOP) where the class of the executing object has an annotation of the given type.

用于匹配目标类上的注解

// 匹配com.manerfan包下所有被Statistics注解修饰的类的所有方法
within(com.manerfan..*) && @target(com.manerfan.support.Statistics)
注: 只能使用注解的全路径 ,不能使用通配符

初此之外,还可以使用参数匹配

// 匹配com.manerfan包下所有被Statistics注解修饰的类的所有方法
@Before("within(com.manerfan..*) && @target(st)")
public void animalBefore(JoinPoint jp, Statistics st /* 这里代入目标类上的注解,可以获取注解中的值 */) { /* ... */ }
注: 注解只能应用于目标类上,不可应用于接口类,不可应用于方法

@args

Limits matching to join points (the execution of methods when using Spring AOP) where the runtime type of the actual arguments passed have annotations of the given types.

用于匹配方法参数上的注解

// 匹配只有一个参数,且被Statistics注解修饰
@args(com.manerfan.support.Statistics)
// 匹配任意参数且第一个参数被Statistics注解修饰
@args(com.manerfan.support.Statistics,..)
// 匹配任意参数且最后一个参数被Statistics注解修饰
@args(..,com.manerfan.support.Statistics)
注: 只能使用注解的全路径 ,不能使用通配符

初此之外,还可以使用参数匹配

// 匹配com.manerfan包下所有类中所有第一个参数被Statistics注解修饰的方法
@Before("within(com.manerfan..*) && @args(st,..)")
public void animalBefore(JoinPoint jp, Statistics st /* 这里代入目标方法参数上的注解,可以获取注解中的值 */) { /* ... */ }

@within

Limits matching to join points within types that have the given annotation (the execution of methods declared in types with the given annotation when using Spring AOP).

用于匹配被拦截方法所在的类上的注解

// 所有被Statistics注解修饰的类的方法
@within(com.manerfan.support.Statistics)
注: 只能使用注解的全路径 ,不能使用通配符

初此之外,还可以使用参数匹配

// 匹配com.manerfan包下 所有被Statistics注解修饰的类的方法
@Before("within(com.manerfan..*) && @within(st)")
public void animalBefore(JoinPoint jp, Statistics st /* 这里代入目标类的注解,可以获取注解中的值 */) { /* ... */ }
注: 注解只能应用于目标类上,不可应用于接口类,不可应用于方法

@annotation

Limits matching to join points where the subject of the join point (the method being executed in Spring AOP) has the given annotation.

用于匹配被拦截方法上的注解

// 所有被Statistics注解修饰的方法
@annotation(com.manerfan.support.Statistics)
注: 只能使用注解的全路径 ,不能使用通配符

初此之外,还可以使用参数匹配

// 匹配com.manerfan包下 所有被Statistics注解修饰的方法
@Before("within(com.manerfan..*) && @annotation(st)")
public void animalBefore(JoinPoint jp, Statistics st /* 这里代入目标方法上的注解,可以获取注解中的值 */) { /* ... */ }
注: 注解只能应用于目标类上的方法上,不可应用于接口类方法,不可直接应用于目标类

订阅号

阅读 6.9k

推荐阅读
林中小舍
用户专栏

工作中的坑点及经验

51 人关注
41 篇文章
专栏主页