Spring 的两大核心,一是IOC,我们之前已经学习过,并且已经自己动手实现了一个,而令一个则是大名鼎鼎的 AOP,AOP的具体概念我就不介绍了,我们今天重点是要从源码层面去看看 spring 的 AOP 是如何实现的。注意,今天楼主给大家分享的是 XML 配置AOP的方式,不是我们经常使用的注解方式,为什么呢?
有几个原因:
1. Spring AOP 在 2.0 版本之前都是使用的 XML 配置方式,封装的层次相比注解要少,对于我们学习AOP是个很好的例子。
2. 虽然现在是2017年,现在使用SpringBoot 都是使用注解了, 但是底层原理都是一样的,只不过多了几层封装。当然,我们也是要去扒开它的源码的。但不是今天。
3. 楼主也还没有分析注解方式的AOP。-_-|||
我们主要分为几个步骤去理解:
1. 查看源码了解 spring AOP 的接口设计。Advice,PointCut,Advisor。
2. 用一个最简单的代码例子 debug 追踪源码。
那么我们现看第一步:
1. Spring AOP 接口设计
1.1 PointCut (连接点,定义匹配哪些方法)
我们打开 Spring 的源码,查看 PointCut 接口设计:
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
Pointcut TRUE = TruePointcut.INSTANCE;
}
该接口定义了2 个方法,一个成员变量。我们先看第一个方法, ClassFilter getClassFilter()
,该方法返回一个类过滤器,由于一个类可能会被多个代理类代理,于是Spring引入了责任链模式,另一个方法则是 MethodMatcher getMethodMatcher()
,表示返回一个方法匹配器,我们知道,AOP 的作用是代理方法,那么,Spirng 怎么知道代理哪些方法呢?必须通过某种方式来匹配方法的名称来决定是否对该方法进行增强,这就是 MethodMatcher 的作用。还有要给默认的 Pointcut 实例,该实例对于任何方法的匹配结果都是返回 true。
我们关注一下 MethodMatcher 接口:
public interface MethodMatcher {
boolean matches(Method method, @Nullable Class<?> targetClass);
boolean isRuntime();
boolean matches(Method method, @Nullable Class<?> targetClass, Object... args);
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
该接口定义了静态方法匹配器和动态方法匹配器。所谓静态方法匹配器,它仅对方法名签名(包括方法名和入参类型及顺序)进行匹配;而动态方法匹配器,会在运行期检查方法入参的值。静态匹配仅会判别一次,而动态匹配因为每次调用方法的入参都可能不一样,所以每次都必须判断。一般情况下,动态匹配不常用。方法匹配器的类型由isRuntime()返回值决定,返回false表示是静态方法匹配器,返回true表示是动态方法匹配器。
总的来说, PointCut 和 MethodMatcher 是依赖关系,定义了AOP应该匹配什么方法以及如何匹配。
1.2 Advice (通知,定义在链接点做什么)
注意,Advice 接口只是一个标识,什么也没有定义,但是我们常用的几个接口,比如 BeforeAdvice,AfterAdvice,都是继承自它。我们关注一下 AfterAdvice 的子接口 AfterReturningAdvice :
public interface AfterReturningAdvice extends AfterAdvice {
void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable;
}
````
该接口定义了一个方法,afterReturning,参数分别是返回值,目标方法,参数,目标方法类,在目标方法执行之后会回调该方法。那么我们就可以在该方法中执行我们的切面逻辑,BeforeAdvice 也是一样的道理。
<div class="se-preview-section-delimiter"></div>
### 1.3 Advisor (通知器,将 Advice 和 PointCut 结合起来)
有了对目标方法的增强接口 Advice 和 如何匹配目标方法接口 PointCut 接口后,那么我们就需要用一个对象将他们结合起来,发挥AOP 的作用,所以Spring 设计了 Advisor(通知器),经过我们刚刚的描述,我们应该知道了,这个 Advisor 肯定依赖了 Advice 和 PointCut,我们看看接口设计:
<div class="se-preview-section-delimiter"></div>
```java
public interface Advisor {
Advice EMPTY_ADVICE = new Advice() {};
Advice getAdvice();
boolean isPerInstance();
}
还有他的子接口:
public interface PointcutAdvisor extends Advisor {
Pointcut getPointcut();
}
最重要的两个方法,getAdvice,getPointcut。和我们预想的基本一致。
接下来,我们可以停下来思考一下,现在有了这个东西,我们怎么实现面向切面编程;
1. 首先我们需要告诉AOP在哪里进行切面。比如在某个类的方法前后进行切面。
2. 告诉AOP 切面之后做什么,也就是说,我们知道了在哪里进行切面,那么我们也该让spring知道在切点处做什么。
3. 我们知道,Spring AOP 的底层实现是动态代理(不管是JDK还是Cglib),那么就需要一个代理对象,那么如何生成呢?
接下来,我们将通过代码的方式,解答这三个疑惑。
2. 从一个简单的AOP例子
首先,我们需要实现刚刚我们说的3个接口,还有一个目标类,还要一个配置文件。一个一个来。
2.1. Pointcut 接口实现
package test;
import java.lang.reflect.Method;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
public class TestPointcut implements Pointcut {
@Override
public ClassFilter getClassFilter() {
return ClassFilter.TRUE;
}
@Override
public MethodMatcher getMethodMatcher() {
return new MethodMatcher() {
public boolean matches(Method method, Class<?> targetClass, Object[] args) {
if (method.getName().equals("test")) {
return true;
}
return false;
}
public boolean matches(Method method, Class<?> targetClass) {
if (method.getName().equals("test")) {
return true;
}
return false;
}
public boolean isRuntime() {
return true;
}
};
}
}
我们如何定义匹配?只要方法名称是test则对该方法进行增强或者说拦截。
2.2 AfterAdvice 实现
package test;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
public class TestAfterAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method,
Object[] args, Object target) throws Throwable {
System.out.println(
"after " + target.getClass().getSimpleName() + "." + method.getName() + "()");
}
}
我们在方法执行完毕后打印该方法的名称和该目标类的名称。这就是我们做的简单增强。
2.3 Advisor 通知器的实现
package test;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Pointcut;
import org.springfra