本篇介绍aspect ----> spring aop的兼容性,以及spring aop额外提供的功能。
它有两种形式,
第一种形式配合注解语法,普通bean,开启代理。
第二种语法纯xml方式,使用aop:config以及子标签。
额外功能:
spring aop提供了更简便使用load time weave的方式。只需要在任意的spring配置文件中添加<context:load-time-weaver>即可。
1、代理方式
步骤如下:
第一步,编写任意的HelloService以及实现类,由于spring aop使用JDK代理实现,所以必须是接口。略。
第二步,编写任意的HelloAspect, 注解语法。略。
第三步,在任意的spring配置文件中添加如下信息:
<!-- HelloService类 --> <bean id="helloService" class="learn.springcore.aop.HelloServiceImpl"/> <!-- 定义aspect --> <bean id="helloAspect" class="learn.springcore.aop.HelloAspect"/> <!-- 开启aspect代理 --> <aop:aspectj-autoproxy/>
第四步,验证,通过applicationContext获取bean,调用HelloService中的方法。
1.1 原理
上述示例中,核心配置是aspectj-autoproxy,它会创建代理对象。它默认使用的是JDK代理。设置proxy-target-class属性为true,切换为CGlib方式。
它会使spring进行以下四个步骤:
- 首先检查当前bean是否是aspect,依据是类上是否有@Aspect注解
- 其次检查当前bean是否被选取,依据是pointcut的语法规则。
- 若bean被选取,自动创建bean的代理对象proxy,代理对象与bean实现相同的接口。若使用CGLIB,代理对象会继承bean。
- 当获取bean时,返回创建的代理对象proxy。
上述方式的缺陷是:
由于是代理方式,所以必须是接口,所以join point类型只能是call,其他类型均不支持。
aspect与join point选择的Java类必须都是已注入到spring 容器中的bean。
运行target的方法时,内部调用的方法都无法再作为join point,无法被选择。代理时只适用于该方法,例如target有test方法,它内部调用hello方法,代理只适用于test方法,其内部的方法不再通过代理对象,即在test方法内部调用hello方法,不会再经过代理对象。
1.2 兼容性
1.2.1 aspect
对于association,只支持isSingleton,perthis, pertarget。其他均不支持。
优先级不再使用@DeclarePrecedence,而是实现Order接口,值越小,优先级越高。
privileged关键字和特殊方法在xml中均不适用。
1.2.2 pointcut
只支持execution类型的join point,其他均不支持。
收集上下文时,只支持target(), this(), args(), within(), @target, @this, @args, @within,其他均不支持。
spring aop额外提供的bean()语法选择容器中的bean。
bean(标识): 根据bean的id,name,alias选取。
bean(*Controller || *Service || *Repository):根据bean的类型,由于类型通常在类名上体现,例如Controller通常都以Controller为后缀,所以也可以理解为根据类的名称。
bean(urlPattern):bean的类型为Controller,根据urlPattern选择Controller。
bean(prefix/*/suffix):bean的标识或类名以prefix为前缀,suffix为后缀。
1.2.3 advice
advice语法全部支持。
唯一区别在around类型的advice中运行proceed方法。
注解语法是不需要传递任何参数的,即proceed会运行join point选择的方法。参数是自动传递的。
spring aop方式下需要将参数传入proceed方法中,默认会获取this, target收集到的参数。可以调用ProceedJoinPoint的getArgs方法,方法的返回值为Object[]。并运行proceed(object[])。
2、XML
<!-- XML注解方式使用Aspect --> <aop:config proxy-target-class="false"> <!-- 定义全局的pointcut --> <aop:pointcut expression="" id=""/> <!-- 定义aspect --> <aop:aspect ref="aspect bean" order="1"> <!-- 定义只使用当前advice的aspect --> <aop:pointcut expression="" id=""/> <!-- 定义before advice --> <aop:before pointcut-ref="" method=""/> </aop:aspect> </aop:config>
2.1 aspect标签
由一组pointcut, advice组成。
ref表示aspect的bean ID, order定义aspect的优先级,实现Order接口,值越小,优先级越高
2.1.1 pointcut标签
id用于指定pointcut的标识,方便advice引用。
expression指定pointcut表达式,只支持execution类型的Pointcut,收集上下文的方式有限,参考pointcut兼容性。
在aspect之外定义的pointcut为作用域为全局的,aspect之内定义的只适用于当前aspect。
2.1.2 advice标签
全部类型都支持。
pointcut-ref引用已定义的pointcut。
pointcut属性直接定义pointcut表达式。
method,advice对应的Java方法名称,等价于注解方式下在方法上添加@Advice相关注解。
arg-names:对应@Before注解中的argName属性,用于收集上下文。
returning属性只适用于after-returning,收集的方法返回值。
throwing属性只适用于after-throwing,收集的异常类型。
2.1.3 declare parents标签
只适用于接口,并提供默认的接口实现类。它的配置如下:
<!-- 定义aspect --> <aop:aspect order="2"> <!-- 定义declare parents --> <aop:declare-parents types-matching="" implement-interface="" default-impl=""/> </aop:aspect>
传统的aspect语法格式为:declare parents : “类签名” implements XXInterface
它们之间的映射关系为:
- declare-parents对应declare parents关键字。
- type-matching:对应类签名。
- implement-interface:对应接口。
- default-impl:接口的默认实现类,无对应值,在传统Aspect使用static aspect impl语法添加接口的默认实现。
3、load time
Spring简化了load time weaving的使用,默认情况下需要在虚拟机中添加agent选项。spring只需要配置<context:load-time-weaver>。
4、对比
通常不使用xml方式,即配置aop-config,是因为其只支持execution,许多功能都不支持。
建议使用注解语法,并配合spring的load time,最简单的一种方式。
注解语法支持了传统语法的大部分功能,若要实现其他功能,选择传统语法,传统语法的功能是最强大的。