切点表达式函数
Spring支持9个@AspectJ切点表达式函数,它们用不同的方式描述目标类的连接点。根据描述对象的不同,可以分为4种类型:
- 方法切点函数:通过描述目标类方法的信息定义连接点
- 方法入参切点函数:通过描述目标类方法入参的信息定义连接点
- 目标类切点函数:通过描述目标类类型的信息定义连接点
- 代理类切点函数:通过描述目标类的代理类的信息定义连接点
package com.smart.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface NeedTest{
boolean value() default false;
}
package com.smart;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Monitorable {
}
切点函数
类别 | 函数 | 入参 | 说明 |
---|---|---|---|
方法切点函数 | execution( ) | 方法匹配模式串 | 表示满足某一匹配模式的所有目标类方法连接点。如execution(*greetTo(…))表示所有目标类中的greetTo( )方法 |
方法切点函数 | @annotation( ) | 方法注解类名 | 表示标注了特定注解的目标类方法连接点。如@annotation(com.smart.anno.NeedTest)表示任何标注了@NeedTest注解的目标类方法 |
方法入参切点函数 | args( ) | 类名 | 通过判别目标类方法运行时入参对象的类型定义指定连接点。如args(com.smart.Waiter)表示所有有且仅有一个按类型匹配于Waiter入参的方法。 |
方法入参切点函数 | @args( ) | 类型注解类名 | 通过判别目标类方法运行时入参对象的类是否标注特定注解来指定连接点。如@args(com.smart.Monitorable)表示任何这样的一个目标方法:它有一个入参且入参对象的类标注@Monitorable注解。 |
目标类切点函数 | within( ) | 类名匹配串 | 表示特定域下的所有连接点。如within(com.smart.service.*)表示com.smart.service包中的所有连接点,即包中所有类的所有方法;而within(com.smart.service.*Service)表示在com.smart.service包中所有以Service结尾的类的所有连接点。 |
目标类切点函数 | target( ) | 类名 | 假如目标类按类型匹配于指定类,则目标类的所有连接点匹配这个切点。如通过target(com.smart.Waiter)定义的切点、Waiter及Waiter实现类NaiveWaiter中的所有连接点都匹配该切点。 |
目标类切点函数 | @within( ) | 类型注解类名 | 假如目标类按类型匹配于某个类A,且类A标注了特定注解,则目标类的所有连接点匹配这个切点。如@within(com.smart.Monitorable)定义的切点,假如Waiter类标注了@Monitorable注解,则Waiter及Waiter实现类NaiveWaiter的所有连接点都匹配这个切点。 |
目标类切点函数 | @target( ) | 类型注解类名 | 假如目标类标注了注解,则目标类的所有连接点都匹配该切点。如@target(com.smart.Monitorable),假如NaiveWaiter标注了@Monitorable,则NaiveWaiter的所有连接点都匹配这个切点。 |
代理类切点函数 | this( ) | 类名 | 代理类按类型匹配于指定类,则被代理的目标类的所有连接点都匹配该切点。 |
@AspectJ支持3种通配符
- *:匹配任意字符,但它只能匹配上下文中的一个元素。
- …:匹配任意字符,可以匹配上下文中的多个元素,但在表示类时,必须和*联合使用,而在表示入参时则单独使用。
- +:表示按类型匹配指定类的所有类,必须跟在类名后面。
逻辑运算符
切点表达式由切点函数组成,切点函数之间还可以进行逻辑运算,组成复合切点。
- &&:与操作符,Spring提供了一个等效的运算符“and”。
- ||:或操作符,Spring提供了一个等效的运算符“or”。
- !:非操作符,Spring提供了一个等效的运算符“not”。
如果not位于切点表达式的开头,则必须在开头添加一个空格,否则产生解析错误。
@AspectJ提供的几个增强注解
- @Before
- @AfterReturning
- @Around
- @AfterThrowing
- @After
Final增强,不管是抛出异常还是正常退出,该增强都会得到执行。 - @DeclareParents
引介增强,相当于IntroductionInterceptor。
切点函数详解
execution( )
execution( )是最常用的切点函数,语法:
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)
返回类型模式、方法名模式、参数模式是必须的,其他项是可选的。
通过方法签名定义切点
- execution(public * *(…)):匹配所有目标类的public方法。第一个*代表返回类型;第二个*代表方法名;…代表任意入参的方法。
- execution(* *To(…)):匹配目标类所有以To为后缀的方法。第一个*代表返回的类型;*To代表任意以To为后缀的方法。
通过类定义切点
- execution(* com.smart.Waiter.*(…)):匹配Waiter接口的所有方法。第一个*代表返回任意类型;com.smart.Waiter.*代表Waiter接口中的所有方法。
- execution(* com.smart.Waiter+.*(…)):匹配Waiter接口及其所有实现类的方法。
通过类包定义切点
在类名模式串中,“.*”表示包下的所有类,而“…*”表示包、子孙包下的所有类。
- execution(* com.smart.Waiter.*(…)):匹配com.smart包下所有类的所有方法。
- execution(* com.smart.Waiter…*(…)):匹配com.smart包、子孙包下所有类的所有方法。
- execution(* com.*.*Dao.find*(…)):匹配包名前缀为com的任何包下类名后缀为Dao的方法,方法名必须以find为前缀。
通过方法入参定义切点
切点表达式中的方法入参部分比较复杂,可以使用“*”和“…”通配符。“*”表示任意类型的参数;而“…”表示任意类型的参数且参数个数不限。
- execution(* joke(String,int)):匹配joke(String,int)方法,且joke( )方法的第一个入参是String,第二个入参是int。
- execution(* joke(String,*)):匹配目标类中的joke( )方法,该方法的第一个入参为String,第二个入参可以为任意类型。
- execution(* joke(String,…)):匹配目标类中的joke( )方法,方法拥有一个入参,且入参是Object类型或该类的子类。
args( )和@args( )
args( )函数的入参是类名,而@args( )函数的入参必须是注解类的类名。
args( )
该函数接收一个类名,表示目标类方法入参对象是指定类时,切点匹配。
@args( )
该函数接收一个注解类的类名,当方法的运行时入参对象标注了指定的注解时,匹配切点。
注解点不高于入参类型点。p288
within( )
within( )函数定义的连接点是针对目标类而言的。within( )所指定的连接点最小范围只能是类。
within( )函数的语法:
with(<类匹配模式>)
- within(com.smart.NaiveWaiter):匹配目标类NaiveWaiter的所有方法。不能是接口,因为接口不能实例化。
- within(com.smart.*):匹配com.smart包中的所有类,但不包括子孙包。
- within(com.smart…*):匹配com.smart包及子孙包中的类。
@within( )和@target( )
@target(M)匹配任意标注了@M的目标类,而@within(M)匹配标注了@M的类及子孙类。
如果标注@M注解的是一个接口,则所有实现该接口的类并不匹配@within(M)。因为@within( )、@target( )及@annotation( )函数都是针对目标类而言的,而非针对运行时的引用类型而言的。
target( )和this( )
target(M)表示如果目标类按类型匹配于M,则目标类的所有方法都匹配切点。
this(M)函数判断代理对象的类是否按类型匹配于指定类,如果匹配,则代理对象的所有连接点匹配切点。
命名切点
切点直接声明在增强方法处,这种切点声明方式称为匿名切点,匿名切点只能在声明处使用。
import org.aspectj.lang.annotation.Pointcut;
public class TestNamePointcut {
// 通过注解方法inPackage()对该切点进行命名,方法可视域修饰符为private,表明该命名切点只能在本切面中使用
@Pointcut("within(com.smart.*)")
private void inPackage(){}
// 通过注解方法greetTo()对该切点进行命名,方法可视域修饰符为protected,表明该命名切点可以在当前包中的切面类、子切面类中使用
@Pointcut("execution(* greetTo(..)))")
protected void greetTo(){}
// 引用命名切点定义的切点,本切点也是命名切点,它对应的可视域为public
@Pointcut("inPackage() and greetTo()")
public void inPkgGreetTo(){}
}
命名切点的使用类方法作为切点的名称,方法的访问修饰符控制了切点的可引用性。
//------------引用命名切点----------//
@Before("TestNamePointcut.inPkgGreetTo()")
public void pkgGreetTo(){
System.out.println("--pkgGreetTo() executed!--");
}
@Before("!target(com.smart.NaiveWaiter) && TestNamePointcut.inPkgGreetTo()")
public void pkgGreetToNotNaiveWaiter(){
System.out.println("--pkgGreetToNotNaiveWaiter() executed!--");
}
增强织入的顺序
- 如果增强在通一个切面类中声明,则依照增强在切面类中定义的顺序进行织入。
- 如果增强位于不同的切面类中,且这些切面类都实现了org.springframework.core.Ordered接口,则由接口方法的顺序号决定(顺序小的先织入)
- 如果增强位于不同的切面类中,且这些切面类没有实现org.springframework.core.Ordered接口,则织入的顺序是不确定的。
访问连接点信息
AspectJ使用org.aspectj.lang.JointPoint接口表示目标类连接点对象。如果是环绕增强,则使用org.aspectj.lang.ProceedingJoinPoint表示连接点,该类是JoinPoint的子接口。
JoinPoint
- java.lang.Object[] getArgs():获取连接点方法运行时的入参列表
- Signature getSignature():获取连接点的方法签名对象
- java.lang.Object getTarget():获取连接点所在的目标对象
- java.lang.Object getThis():获取代理对象本身
ProceedingJoinPoint
ProceedingJoinPoint继承于JoinPoint子接口,它新增了两个用于执行连接点方法的方法。
- java.lang.Object proceed() throws java.lang.Throwable:通过反射执行目标对象的连接点处的方法
- java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable:通过反射执行目标对象连接点处的方法,不过使用新的入参替换原来的入参。