Spring AOP 切点表达式

1.概述

本文主要记录一下 Spring AOP 中 Pointcut 切点表达式。

关于 AOP 及切点相关的概念可以参考 Spring源码解读——AOP

2. 用法

在基于注解的 AOP 开发过程中,切点表达式主要用于 @Pointcut 注释的值:

1
2
@Pointcut("within(@org.springframework.stereotype.Repository *)")
public void repositoryClassMethods() {}

方法声明称为切入点签名,它定义了一个切点范围,后续在创建 Advice 时可以直接引用这个签名,来确定 Advice 的拦截范围。

1
2
3
4
@Around("repositoryClassMethods()")
public Object measureMethodExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
...
}

也可以通过 XML aop:pointcut 配置的方式定义:

1
2
3
<aop:config>
<aop:pointcut id="anyDaoMethod" expression="@target(org.springframework.stereotype.Repository)"/>
</aop:config>

3. 切入点指示符

切入点表达式以pointcut designator(PCD)开头,告诉Spring AOP要匹配的关键字。下面将介绍常用的指示符,例如方法的执行,类型,方法参数或注解。

3.1 execution

execution 是 Spring AOP 中最主要也是最常用的 PCD,它匹配方法执行点:

1
@Pointcut("execution(public String com.doleje.dao.FooDao.findById(Long))")

此示例切入点将严格匹配FooDao类的findById方法的执行。如果我们想更灵活的控制,比如匹配FooDao类的所有方法,它们可能具有不同的签名,返回类型和参数,那么还可以使用通配符:

1
@Pointcut("execution(* com.doleje.dao.FooDao.*(..))")

这里第一个 * 匹配任何返回值,第二个 * 任何方法名称,*(..)* 模式匹配任意数量的参数(零或更多)。

3.2 within

within 用于匹配某个类或者包内的所有操作,比如我们要实现上一节中的匹配 FooDao 中所有方法还可以用如下的方式:

1
@Pointcut("within(com.doleje.dao.FooDao)")

我们也可以匹配com.doleje 包或子包中的任何类型。

1
@Pointcut("within(com.doleje..*)")

3.3 thistarget

this 切点函数则通过判断 代理类 是否按类型匹配指定类来决定是否和切点匹配。 用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配。 this中使用的表达式必须是类型全限定名,不支持通配符。

target 匹配目标对象的类型,即被代理对象的类型,例如A继承了B接口,则使用target(“B”),target(“A”) 均可以匹配到A(因为 A 也是 B 接口的目标对象)。

这两个概念确实比较绕,也很容易弄混。但简单的,你可以记住如下的规则,假设目标类实现了一个接口:

1
2
3
public class FooDao implements BarDao {
...
}

由于接口的存在,在这种情况下,Spring AOP将使用基于JDK的动态代理技术,你应该使用target ,因为代理对象将是Proxy类的实例并实现BarDao接口:

1
@Pointcut("target(com.doleje.dao.BarDao)")

另一方面,如果FooDao没有实现任何接口或者 proxyTargetClass 属性设置为true,则代理对象将是FooDao的子类,并且可以使用 this

1
@Pointcut("this(com.doleje.dao.FooDao)")

3.4 args

args 用于匹配特定方法参数:

1
@Pointcut("execution(* *..find*(Long))")

此切入点匹配以find开头的任何方法,并且只有一个Long类型的参数。如果我们想要一个方法与任意数量的参数匹配但是具有Long类型的第一个参数,我们可以使用以下表达式:

1
@Pointcut("execution(* *..find*(Long,..))")

3.5 @target

@target 用于匹配某些有特定注解的类,注意,不要与 target 混淆

1
`@Pointcut``(``"@target(org.springframework.stereotype.Repository)"``)`

3.6 @args

@args 匹配任何一个只接受一个参数的方法,且方法运行时传入的参数持有该注解的点。

1
2
@Pointcut("@args(com.doleje.aop.annotations.Entity)")
public void methodsAcceptingEntities() {}

可以通过 Advice 提供的 JoinPoint 来访问具体的参数以及注解:

1
2
3
4
@Before("methodsAcceptingEntities()")
public void logMethodAcceptionEntityAnnotatedBean(JoinPoint jp) {
logger.info("Accepting beans with @Entity annotation: " + jp.getArgs()[0]);
}

3.7 @within

@within 将匹配具有给定注解的类型中的连接点:

1
@Pointcut("@within(org.springframework.stereotype.Repository)")

这相当于:

1
@Pointcut("within(@org.springframework.stereotype.Repository *)")

3.8 @annotation

@annotation 将匹配添加了某个注解的方法,比如我们定义一个 @Loggable 注解:

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Loggable {
}

然后针对有这个注解的方法添加一个切点:

1
2
@Pointcut("@annotation(com.doleje.aop.annotations.Loggable)")
public void loggableMethods() {}

最后我们写一个 Advice 来对匹配到这个切点的方法进行一些切面处理:

1
2
3
4
5
@Before("loggableMethods()")
public void logMethod(JoinPoint jp) {
String methodName = jp.getSignature().getName();
logger.info("Executing method: " + methodName);
}

4. 切入点表达式的组合

Pointcut expressions 是可以使用&&|| 以及 ! 来组合的:

1
2
3
4
5
6
7
8
@Pointcut("@target(org.springframework.stereotype.Repository)")
public void repositoryMethods() {}

@Pointcut("execution(* *..create*(Long,..))")
public void firstLongParamMethods() {}

@Pointcut("repositoryMethods() && firstLongParamMethods()")
public void entityCreationMethods() {}