引言

面向切面的程序设计(Aspect-oriented programming,AOP,又译作面向方面的程序设计、剖面导向程序设计)是计算机科学中的一种程序设计思想,旨在将横切关注点与业务主体进行进一步分离,以提高程序代码的模块化程度。通过在现有代码基础上增加额外的通知(Advice)机制,能够对被声明为“切点(Pointcut)”的代码块进行统一管理与装饰,如“对所有方法名以‘set*’开头的方法添加后台日志”。该思想使得开发人员能够将与代码核心业务逻辑关系不那么密切的功能(如日志功能)添加至程序中,同时又不降低业务代码的可读性。面向切面的程序设计思想也是面向切面软件开发的基础。

1、术语

1.1、连接点 Joinpoint

指程序执行过程中的点,如方法调用,在Spring AOP中支持方法级别的连接点。
org.aopalliance.intercept.Joinpoint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

/**
* 这个接口表示一个通用的运行时连接点(在AOP术语中)。
*
* 运行时连接点是发生在静态连接点(即程序中的一个位置)上的事件。
* 例如,调用是方法上的运行时连接点(静态连接点)。
* 可以使用getStaticPart()方法通用地检索给定连接点的静态部分。
*
* 在截取框架的上下文中,运行时连接点是对可访问对象(方法、构造函数、字段)的访问的具体化,即连接点的静态部分。
* 它被传递给安装在静态连接点上的拦截器。
*
* @author Rod Johnson
* @see Interceptor
*/
public interface Joinpoint {

/**
* 继续进入链中的下一个拦截器。
* 此方法的实现和语义取决于实际的连接点类型(参见子接口)。
* @return 参见子接口的proceed定义
* @throws Throwable 如果连接点抛出异常
*/
Object proceed() throws Throwable;

/**
* 返回包含当前连接点静态部分的对象。
* 例如,调用的目标对象。
* @return 对象(如果可访问对象是静态的,则可以为空)
*/
Object getThis();

/**
* 返回此连接点的静态部分。
* 静态部分是一个可访问的对象,在其上安装了一系列拦截器。
*/
AccessibleObject getStaticPart();

}

其中最重要的方法是proceed方法,用于执行拦截器链中的下一个拦截器逻辑。
每个方法的调用都是一个连接点,可以看一下Joinpoint接口的继承体系图:
Joinpoint.png

连接点了解了,之后就是要看过滤连接点的知识了。

1.2、切点 Pointcut

核心Spring切入点抽象。切入点由ClassFilter和MethodMatcher组成。这些基本术语和切入点本身都可以组合起来构建组合 (例如,通过org.springframework.aop.support.ComposablePointcut)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface Pointcut {
//返回这个切入点的类过滤器。
ClassFilter getClassFilter();
//返回这个切入点的MethodMatcher。
MethodMatcher getMethodMatcher();
//总是匹配的规范切入点实例。
Pointcut TRUE = TruePointcut.INSTANCE;
}
public interface ClassFilter {
boolean matches(Class<?> clazz);
ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
public interface MethodMatcher {
boolean matches(Method method, Class<?> targetClass);
boolean matches(Method method, Class<?> targetClass, Object... args);
boolean isRuntime();
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}

只要实现了matches方法即可对连接点进行选择,一般通过AspectJ表达式实现。

AspectJExpressionPointcut.png

AspectJ表达式:

1
execution (* com.sample.service.impl..*.*(..))
  1. execution()
    表达式主体。
  2. 第一个
    表示返回类型,
    号表示所有的类型。
  3. 包名
    表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
  4. 第二个
    表示类名,
    号表示所有的类。
  5. (..)
    最后这个星号表示方法名,
    号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

要拦截的方法找到之后就该进行增强逻辑了。

1.2、通知 Advice

这个就是定义横切逻辑的地方了:

1
2
//用于通知的标记接口。实现可以是任何类型的通知,比如拦截器。
public interface Advice {}

通知类型:

  • 前置通知(Before advice)- 在目标方便调用前执行通知
  • 后置通知(After advice)- 在目标方法完成后执行通知
  • 返回通知(After returning advice)- 在目标方法执行成功后,调用通知
  • 异常通知(After throwing advice)- 在目标方法抛出异常后,执行通知
  • 环绕通知(Around advice)- 在目标方法调用前后均可执行自定义逻辑

贴一下Advice的重要类继承体系图:
Advice.png

现在有了Pointcut和Advice,但是他们还是分离的,需要整合。

1.3、切面 Aspect

通过Aspect整合切点和通知,就可以解决对什么方法在前置或者后置或者环绕进行通知。Spring中没有Aspect接口,但是有相同功能的PointcutAdvisor:

1
2
3
4
5
6
7
8
public interface Advisor {
Advice EMPTY_ADVICE = new Advice() {};
Advice getAdvice();
boolean isPerInstance();
}
public interface PointcutAdvisor extends Advisor {
Pointcut getPointcut();
}

在看一些其实现者AspectJPointcutAdvisor的继承体系图:
AspectJPointcutAdvisor.png

有了连接点、切点、通知、以及切面,就差一个最终的执行。

1.4、织入 Weaving

织入就是在切点的引导下,将增强逻辑插入到方法调用上。Spring是通过BeanPostProcessor实现的。
通过实现这个接口,在bean初始化完成后,Spring通过切点对bean中的方法进行匹配,如果匹配成功,为这个bean生成代理对象,并将代理对象返回给容器,容器向后置处理器输入bean对象,得到bean对象的代理,这样就完成了织入。
AspectJWeavingEnabler的继承图谱:
AspectJWeavingEnabler.png

2、简单总结

通过这些术语的分析,大概能了解AOP的整个流程。后期通过简单的例子开始源码分析。
最后贴个图:

aop.png

tencent.jpg