一、一些概念
1.1、通用功能与业务功能解耦
- 横向关注点:软件开发中,散步于应用中多处的功能被称为横向关注点。例如:日志、安全校验、过滤、异常处理等
- 核心关注点:业务处理的主要流程是核心关注点。
- Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来;但是又能相互关联
1.2、术语
案例:抄表员为某些地区的住户的电表记录用电量。
- 通知(Advice):定于了切面是什么以及何时使用,除了描述切面要完成的工作,还有何时执行这个工作(记录用电量)
- 类型:前置通知、后置通知、返回通知、异常通知、环绕通知。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。
- 切点(Pointcut):定义了何处,缩小了切面所通知的连接点的范围,切点的定义会匹配通知所要织入的一个或多个连接点(如:指定某一区域的住户)。是指定一个通知将被应用的一系列连接点的集合,一般使用正则表达式来指定。 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上
- 切面(切点+通知)(Aspect):通知和切点的结合。切点和通知共同定义了切面的全部内容-它是什么、在何时和何处完成其功能
- 连接点:是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至是修改字段时(房屋内安装的电表,是为抄表员去读取数据)
- 引入(Introduction):允许我们向现有的类添加新方法和属性。
- 织入(Weaving):把切面应用到目标对象并创建新的代理对象的过程
- 目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。POJO
- AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
1.3、spring对aop的支持
- 基于代理的经典Spring AOP
- 纯POJO切面
- @AspectJ注解驱动的切面
- 注入式AspectJ切面
Spring AOP是构建在动态代理基础上,因此,Spring对AOP的支持局限于方法的拦截。
二、如何使用aop
2.1、aop配置方式:
- 借鉴了AspectJ的切面,提供注解的方式驱动AOP。
- 基于xml配置,例如纯POJO转换成切面,需要xml中进行配置
2.2、spring运行执行切面的过程
- 生成代理类,包裹切面。spring在运行期间将切面织入spring管理的bean中。
- 代理类封装了目标类,并拦截被通知方法的调用,执行切面逻辑,再将调用转发给真正的目标bean
2.3、代理对象的生成
- Spring提供了两种方式来生成代理对象: JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定
- 默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。
类型 | 原理 | 优点 | 缺点 |
---|---|---|---|
JDK动态代理 | 通过java.lang.reflect.Proxy动态生成代理类;代理类要实现InvocationHandler接口;只能基于接口进行 | 必须要有接口,使系统更加松耦合 | 需要为每一个目标类生成新的接口 |
Cglib动态代理 | 采用底层字节码技术,可以为一个类创建子类,并在子类中采用方法拦截技术拦截所有父类方法调用,并织入横切逻辑。CGlib和JDK原理类似,也是通过反射调用目标对象的方法。 | 代理类与目标类是继承关系,不需要接口存在。只是需要生成子类 | 没有使用接口,所以系统耦合性没有JDK动态代理好 |
2.4、切点的编写
- (1)、切点指示器
- execution指示器是实际执行匹配的。而其他指示器都是用来限制匹配的。
- execution指示器是实际执行匹配的。而其他指示器都是用来限制匹配的。
- (2)、编写
- “ * ”开始,说明我们不关心方法的返回类型
- “(…)”,参数列表是两个点,表明切点选择任意的prform方法,无论方法参数是什么。
- (3)、使用@Pointcut注解声明频繁使用的切点表达式
2.5、切面的编写
- 切面的类,使用@AspectJ注解标注该类是一个切面类。
- 四种通知类型方法:
- @After:通知方法会在目标方法返回或者异常的时候调用
- @AfterReturning:通知方法会在目标方法返回的时候调用
- @AfterThrowing:通知方法会在目标方法异常的时候调用
- @Before:通知方法会在目标方法调用之前调用
- @Around:通知方法会将目标方法封装起来。最强大的通知类型,环绕通知是接受ProceedingJoinPoint作为参数,当我们再通知中通过它调用被通知的方法,就需要调用ProceedingJoinPoint的proceed()方法。若不调用的话,通知实际上是会阻塞被通知方法的调用的。
2.6、启动自动代理的配置
- (1)、使用JavaConfig的方式,在配置类的类级别上通过使用EnableAspctJ-AutoProxy注解启用自动代理功能
- (2)、使用xml配置的方式,使用aop命名空间的< aop:aspectj-autoproxy >
2.7、引入新功能
package concert; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
@Aspect
public class EncoreableIntroducer {
@DeclareParents( value =" concert.Performance +", defaultImpl = DefaultEncoreable.class)
public static Encoreable encoreable;
}
- @DeclareParents 注 解 由 三 部 分 组 成:
- value 属 性 指 定 了 哪 种 类 型 的 bean 要 引 入 该 接 口。 在 本 例 中, 也 就 是 所 有 实 现 Performance 的 类 型。( 标 记 符 后 面 的 加 号 表 示 是 Performance 的 所 有 子 类 型, 而 不 是 Performance 本 身。)
- defaultImpl 属 性 指 定 了 为 引 入 功 能 提 供 实 现 的 类。 在 这 里, 我 们 指 定 的 是 DefaultEncoreable 提 供 实 现。
- @DeclareParents 注 解 所 标 注 的 静 态 属 性 指 明 了 要 引 入 了 接 口。 在 这 里, 我 们 所 引 入 的 是 Encoreable 接 口。