1.execution
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)
throws-pattern?)
execution(修饰符匹配式? 返回类型匹配式 类名匹配式? 方法名匹配式(参数匹配式) 异常匹配式?)
代码块中带?符号的匹配式都是可选的,对于execution必不可少的只有三个:
- 返回值匹配值
- 方法名匹配式
- 参数匹配式
举例分析: execution(public * ServiceDemo.*(..))
匹配public修饰符,返回值是*
,即任意返回值类型都行,ServiceDemo是类名匹配式不一定要全路径,只要全局依可见性唯一就行,.*
是方法名匹配式,匹配所有方法,..
是参数匹配式,匹配任意数量、任意类型参数的方法。
再举一些其他例子:
execution(* cn.cychee.service..*.*(..))
: 匹配cn.cychee.service及其子包下的任意方法。
execution(* joke(Object+)))
:匹配任意名字为joke的方法,且其动态入参是是Object类型或该类的子类。
execution(* joke(String,..))
:匹配任意名字为joke的方法,该方法 一个入参为String(不可以为子类),后面可以有任意个入参且入参类型不限
execution(* com..*.*Dao.find*(..))
: 匹配指定包下find开头的方法
execution(* cn.cychee.Waiter+.*(..))
: 匹配cn.cychee包下Waiter及其子类的所有方法。
2.within
筛选出某包下的所有类,注意要带有*
。
within(cn.cychee.service.*)
cn.cychee.service包下的类,不包括子包within(cn.cychee.service..*)
cn.cychee.service包下及其子包下的类
3.this
常用于命名绑定模式
。对由代理对象的类型进行过滤筛选。
如果目标类是基于接口实现的,则this()
中可以填该接口的全路径名,否则非接口实现由于是基于CGLIB
实现的,this中可以填写目标类的全路径名。
this(cn.cychee.service.AccountService)
: 代理类是cn.cychee.service.AccountService或其子类
。
使用@EnableAspectJAutoProxy(proxyTargetClass = true)
可以强制使用CGLIB。否则默认首先使用jdk动态代理,jdk代理不了才会用CGLIB。
4.target
this作用于代理对象,target作用于目标对象。
target(cn.cychee.service.AccountService)
: 被代理类(目标对象)是cn.cychee.service.AccountService或其子类
5.args
常用于对目标方法的参数匹配。一般不单独使用,而是配合其他表达式来使用。args可以使用命名绑定模式,如下举例:
@Aspect // 切面声明
@Component // 注入IOC
@Slf4j
class AspectDemo {
@Around("within(per.aop.*) && args(str)") // 在per.aop包下,且被代理方法的只有一个参数,参数类型是String或者其子类
@SneakyThrows
public Object logAspect(ProceedingJoinPoint pjp, String str) {
String signature = pjp.getSignature().toString();
log.info("{} start,param={}", signature, pjp.getArgs());
Object res = pjp.proceed();
log.info("{} end", signature);
return res;
}
}
-
如果args中是参数名,则配合切面(advice)方法的使用来确定要匹配的方法参数类型,比如logAspect中str类型设置为String,则表达式匹配的是String。同时str可以直接获取到参数具体值。
-
如果args中是类型,例如@Around(“within(per.aop.*) && args(String)”),则可以不必使用切面方法来确定类型,但这样也不能使用参数绑定传递了。
虽然args()支持+
符号,但args()本身就支持子类通配。
和带参数匹配execution区别
举个例子: args(cn.cychee.Waiter)
等价于 execution(* *(cn.cychee.Waiter+))
。而且execution不能支持带参数的advice。
6.@target
当一个Service有多个子类时, 某些子类需要打日志,某些子类不需要打日志时可以如下处理(配合java多态)
筛选出具有给定注解的被代理对象(是对象不是类,@target是动态的),如下自定义一个注解LogAble
//全限定名: annotation.LogAble
@Target({ElementType.TYPE,ElementType.PARAMETER}) // 支持在方法参数、类上注
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAble {
}
假如需要“注上了这个注解的所有类的的public方法“都打日志的话,可以如下这么写
@Around("@target(annotation.LogAble) && execution(public * *.*(..))")
// 自定义日志逻辑
7.@args
对于目标方法参数的运行时类型要有@args指定的注解。是方法参数的类型上有指定注解,不是方法上带注解。
比如func(MyClass param)
,匹配的是这个MyClass
上是不是带注解。
使用场景: 假如参数类型有多个子类,只有某个子类才可以匹配该表达式。
@args(cn.cychee.aop.jargs.demo.Anno)
: 匹配1个参数,且第1个参数运行时需要有Anno注解
@args(cn.cychee.aop.jargs.demo.Anno,..)
:匹配一个或多个参数,第一个参数运行时需要带上Anno注解。
@args(cn.cychee.aop.jargs.demo.Anno,cn.cychee.aop.jargs.demo.Nex)
: 一参匹配Anno,二参匹配Nex 。
8.@within
非运行时类型的的@target。@target关注的是被调用的对象,@within关注的是调用的方法所在的类。
@target 和 @within 的不同点:
- @target(注解A):判断被调用的目标对象中是否声明了注解A,如果有,会被拦截
- @within(注解A): 判断被调用的方法所属的类中是否声明了注解A,如果有,会被拦截
9.@annotation
匹配有指定注解的方法(注解作用在方法上面)
@within和@target针对类的注解,@annotation是针对方法的注解
10.bean
根据beanNam来匹配。支持*
通配符。
bean(*Service) // 匹配所有Service结尾的Service
11.组合使用
表达式支持&& || !
三种运算符。||表示或。!表示非。
命名绑定模式
上文中的@Around("within(per.aop.*) && args(str)")
示例就是使用了命名绑定模式,在表达式中写上变量名,在方法上对变量名的类型进行限定。
@Around("within(per.aop.*) && args(str)")
public Object logAspect(ProceedingJoinPoint pjp, String str) { ...}
命名绑定模式只支持target、this、args三种表达式
argNames
观察源码可以发现,所有的Advice注解都带有argNames字段,例如@Around
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Around {
String value();
String argNames() default "";
}
什么情况下会使用这个属性呢,如下举例解释:
@Around(value = "execution(* TestBean.paramArgs(..)) && args(decimal,str,..)&& target(bean)", argNames = "pjp,str,decimal,bean")
@SneakyThrows // proceed会抛受检异常
Object aroundArgs(ProceedingJoinPoint pjp,/*使用命名绑定模式*/ String str, BigDecimal decimal, Object bean) {
// 在方法执行前做一些操作
return pjp.proceed();
}
argNames 必须要和args、target、this标签一起使用。虽然实际操作中可以不带,但官方建议所有带参数的都带,原因如下:
因此如果argNames属性没有指定,那么 Spring AOP 将查看类的调试信息,并尝试从局部变量表中确定参数名。只要使用调试信息(至少是‘-g: vars’)编译了类,就会出现此信息。使用这个标志编译的结果是:
(1)你的代码将会更容易被反向工程
(2)类文件大小将会非常大(通常是无关紧要的)
(3)删除未使用的局部变量的优化将不会被编译器应用。
此外,如果编译的代码没有必要的调试信息,那么 Spring AOP 将尝试推断绑定变量与参数的配对。如果变量的绑定在可用信息下是不明确的,那么一个 AmbiguousBindingException 就会被抛出。如果上面的策略都失败了,那么就会抛出一个 IllegalArgumentException。
12.多提一嘴
Advice的类型有以下几种,也就是切面增强的时机
术语 | 概念 |
---|---|
Before |
在方法被调用之前执行增强 |
After |
在方法被调用之后执行增强 |
AfterReturning |
在方法成功执行之后执行增强 |
AfterThrowing |
在方法抛出指定异常后执行增强 |
Around |
在方法调用的前后执行自定义的增强行为(最灵活的方式) |