往期相关文章:html
10分钟入门SpringAOPspring
PCD(pointcut designators )就是SpringAOP的切点表达式。SpringAOP的PCD是彻底兼容AspectJ的,一共有10种。ide
<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e6e849cf4c0e4d8bb527db4aaddc65e0~tplv-k3u1fbpfcp-zoom-1.image" alt="image-20210108140219721" style="zoom:50%;" />优化
SpringAOP是基于动态代理实现的,如下以目标对象
表示被代理bean,代理对象表示AOP构建出来的bean。目标方法表示被代理的方法。this
execution是最经常使用的PCD。它的匹配式模板以下展现:idea
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) execution(修饰符匹配式? 返回类型匹配式 类名匹配式? 方法名匹配式(参数匹配式) 异常匹配式?)
代码块中带?
符号的匹配式都是可选的,对于execution PCD
必不可少的只有三个:.net
举例分析: execution(public * ServiceDemo.*(..))
匹配public修饰符,返回值是*
,即任意返回值类型都行,ServiceDemo
是类名匹配式~不必定要全路径,只要全局依可见性惟一就行~,.*
是方法名匹配式,匹配全部方法,..
是参数匹配式,匹配任意数量、任意类型参数的方法。代理
再举一些其余例子:调试
execution(* com.xyz.service..*.*(..))
: 匹配com.xyz.service及其子包下的任意方法。 execution(* joke(Object+)))
:匹配任意名字为joke
的方法,且其动态入参是是Object类型或该类的子类。 execution(* joke(String,..))
:匹配任意名字为joke
的方法,该方法 一个入参为String(不能够为子类),后面能够有任意个入参且入参类型不限 execution(* com..*.*Dao.find*(..))
: 匹配指定包下find开头的方法 execution(* com.baobaotao.Waiter+.*(..))
: 匹配com.baobaotao包下Waiter
及其子类的全部方法。筛选出某包下的全部类,注意要带有*
。
within(com.xyz.service.*)
com.xyz.service包下的类,不包括子包within(com.xyz.service..*)
com.xyz.service包下及其子包下的类经常使用于命名绑定模式
。对由代理对象的类型进行过滤筛选。
若是目标类是基于接口实现的,则this()
中能够填该接口
的全路径名,不然~非接口实现~因为是基于CGLIB实现的,this中能够填写目标类
的全路径名。
this(com.xyz.service.AccountService)
: 代理类是com.xyz.service.AccountService或其子类。
使用@EnableAspectJAutoProxy(proxyTargetClass = true)
能够强制使用CGLIB。不然默认首先使用jdk动态代理,jdk代理不了才会用CGLIB。
this做用于代理对象,target做用于目标对象。
target(com.xyz.service.AccountService)
: 被代理类(目标对象)是com.xyz.service.AccountService或其子类
经常使用于对目标方法的参数匹配。通常不单独使用,而是配合其余PCD来使用。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; } }
参数名
,则配合切面(advice)方法的使用来肯定要匹配的方法参数类型。@Around("within(per.aop.*) && args(String)”)
,则能够没必要使用切面方法来肯定类型,但此时也不能使用参数绑定了~见下文~了。虽然args()
支持+
符号,但本省args()
就支持子类通配。
execution
区别举个例子: args(com.xgj.Waiter)
等价于 execution(* *(com.xgj.Waiter+))
。并且execution不能支持带参数的advice。
使用场景举例: 当一个Service有多个子类时, 某些子类须要打日志,某些子类不须要打日志时能够以下处理(配合java多态):
筛选出具备给定注解的被代理对象~是对象不是类,@target是动态的~。以下自定义一个注解LogAble
:
//全限定名: annotation.LogAble @Target({ElementType.TYPE,ElementType.PARAMETER}) // 支持在方法参数、类上注 @Retention(RetentionPolicy.RUNTIME) public @interface LogAble { }
假如须要“注上了这个注解的全部类的的public
方法“都打日志的话~日志逻辑要自定义~,能够以下这么写PCD,固然对应方法的bean要注入到SpringIOC容器中:
@Around("@target(annotation.LogAble) && execution(public * *.*(..))") // 自定义日志逻辑
对于目标方法参数的运行时类型
要有@args
指定的注解。是方法参数的类型上有指定注解,不是方法参数上带注解。
使用场景: 假如参数类型有多个子类,只有某个子类才能够匹配该PCD。
@args(com.ms.aop.jargs.demo1.Anno1)
: 匹配1个参数,且第1个参数运行时须要有Anno1注解@args(com.ms.aop.jargs.demo1.Anno1,..)
匹配一个或多个参数,第一个参数运行时须要带上Anno1注解。@args(com.ms.aop.jargs.demo1.Anno1,com.ms.aop.jargs.demo1.Anno2)
: 一参匹配Anno1,二参匹配Annno2 。非运行时类型的
的@target
。@target关注的是被调用的对象,@within关注的是调用的方法所在的类。
@target 和 @within 的不一样点:
@target(注解A):判断被调用的目标对象
中是否声明了注解A,若是有,会被拦截
@within(注解A): 判断被调用的方法所属的类
中是否声明了注解A,若是有,会被拦截
匹配有指定注解的方法(注解做用在方法上面)
根据beanNam来匹配。支持*
通配符。
bean(*Service) // 匹配全部Service结尾的Service
PCD之间支持,&& || !
三种运算符。上文示例中就使用了&& 运算符。||
表示或(不是短路或)。!
表示非。
上文中的 @Around("within(per.aop.*) && args(str)")
示例就是使用了命名绑定模式,在PCD中写上变量名,在方法上对变量名的类型进行限定。
@Around("within(per.aop.*) && args(str)") public Object logAspect(ProceedingJoinPoint pjp, String str) { ...}
如上举例,str要是String类型或其子类,且方法入参只能有一个。
name binding only allowed in target, this, and args pcds
命名绑定模式只支持target、this、args
三种PCD。
观察源码能够发现,全部的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标签一块儿使用。虽然实际操做中能够不带,但官方建议全部带参数的都带,缘由以下:
所以若是‘ argernames’属性没有指定,那么 Spring AOP 将查看类的调试信息,并尝试从局部变量表中肯定参数名。只要使用调试信息(至少是‘-g: vars’)编译了类,就会出现此信息。使用这个标志编译的结果是:
(1)你的代码将会更容易被反向工程)
(2)类文件大小将会很是大(一般是可有可无的)
(3)删除未使用的局部变量的优化将不会被编译器应用。
此外,若是编译的代码没有必要的调试信息,那么 Spring AOP 将尝试推断绑定变量与参数的配对。若是变量的绑定在可用信息下是不明确的,那么一个 AmbiguousBindingException 就会被抛出。若是上面的策略都失败了,那么就会抛出一个 IllegalArgumentException。
建议全部的advice注解里都带
argNames
,反正idea也会提醒。