一、AOP 概述
AOP 是 Aspect Oriented Programing 的简称,面向切面的编码;
AOP 的工作重心是如何将增强应用到目标对象的连接点上:
第一,通过切点和增强定位到连接点上;
第二,在增强中编写切面的代码;
1.1、连接点 - Joinpoint
Spring 仅支持方法的连接点;
连接点由两个个信息确定:
(1)一是用方法表示的程序 执行点;
(2)二是用相对位置表示的 方位;
如在 Test.foo() 方法执行前的连接点,执行点是 Test.foo(),方位是该方法执行前的位置;
1.2、切点 - Pointcut
切点可以用来定位连接点;
在 Spring 中,切点通过 org.springframework.aop.Pointcut 接口进行描述,使用类和方法作为连接点的查询条件,Spring AOP 的规则解析引擎负责解析切点所设定的查询条件,找到连接点,准备的说是 执行点;
Spring 通过 org.springframework.aop.Pointcut 接口描述切点:
- ClassFilter:定位到某些特定类上;
- MethodMatcher:定位到某些特定的方法上;
1.3、增强 - Advice
增强是织入目标类连接点上的 一段程序代码;
在 Spring 中,增强还拥有一个和连接点有关的信息,就是 执行点的方位,
如 BeforeAdvice、AfterReturningAdvice、ThrowsAdvice 等;
1.4、目标对象 - Target
增强逻辑的织入目标类;
1.5、引介 - Introduction
引介是一种特殊的增强,它可以为类添加一些属性和方法,比如间接的帮业务类实现某个接口;
1.6、织入 - Weaving
织入就是将增强或者引介添加到目标类的具体连接点上的过程;
(1)编译期织入,要求使用特殊的 Java 编译器;
(2)类装载期织入,要求使用特殊的类装载器;
(3)动态代理织入,在运行期为目标类添加增强生成子类的方式;
Spring 采用的是 动态代理织入,而 AspectJ 采用 编译期织入和类装载期织入;
1.7、代理 - Proxy
一个类被 AOP 织入增强后,会产生一个结果类,它是 融合了原类和增强逻辑的代理类;
1.8、切面 - Aspect
切面由 切点 和 增强(引介)组成;
包含了 横切代码 和 连接点 信息;
二、基础知识 - 动态代理技术
Spring AOP 使用动态代理技术在运行期织入增强的代码,它使用了两种代理机制:
(1)一种是基于 JDK 的动态代理;
(2)一种是基于 CGLib 的动态代理;
之所以需要两种动态代理机制,是因为 JDK 本身只提供接口的代理,而不支持类的代理;
二者的区别可以参考我之前的文章:Spring AOP 两种动态代理机制
JDK 的动态代理主要涉及 java.lang.reflect 包中的两个类:
(1)Proxy ;
(2)InvocationHandler(是一个接口);
原始的 AOP 有三个问题:
(1)目标类的所有方法都添加了性能监视横切逻辑,但是我们只希望对业务类中的某些特定的方法添加横切逻辑;
(2)通过硬编码的方式指定了织入横切逻辑的织入点,及在方法开始前还是结束前织入代码;
(3)手工编写代理实例的创建过程,为不同的类创建代理时,需要分别编写相应的创建代码,无法做到通用;
Spring AOP 则是通过 Pointcut 指定哪些类的哪些方法需要织入横切逻辑,通过 Advice 描述横切逻辑和方法的具体织入方位;其通过 切面 Advisor 将 Pointcut 和 Advice 组装起来,这样就可以利用 JDK 和 CGLib 采用统一的方式为目标对象创建织入切面的代理对象了;
三、问题
3.1、spring AOP无法拦截内部方法调用(方法 A 内部调用了本类的方法 B,B没有得到增强)?
方法内部之间调用的时候,不会使用被增强的代理类,而是直接调用未被增强的原类方法;
解决方法有很多,将增强类写入原类中属性中,然后使用该属性调用 B 方法,或者使用如下方法:
(1)配置文件添加 expose-proxy 属性,值为 true;
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true">
(2)内部调用的地方使用,使用前先判断 AopContext.currentProxy() 是否存在;
((类名) AopContext.currentProxy()).方法名