Spring源码解读——AOP

一、基本知识

AOP基本概念

Aspect-Oriented Programming 面向切面编程的简称,Aspect是一种新的模块化机制,用来描述分散在对象、类或方法中的横切关注点(crosscutting concern),从关注点中分离出横切关注点是面向方面程序设计的核心所在。分离关注点使得解决特定领域问题的代码从业务逻辑中独立出来,业务逻辑的代码中不再含有针对特定领域问题代码的调用,业务逻辑同特定领域问题的关系通过方面来封装、维护,这样原本分散在整个应用程序中的变动就可以很好地管理起来。

AOP和OOP的区别

面向方面编程AOP和面向对象编程OOP在字面上虽然非常类似,但却是面向不同领域的两种设计思想。

OOP针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。

而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。

AOP常用的技术以及实现

常用的AOP技术有:

  • AspectJ:源代码和字节码级别的方面编织器,用户需要使用不同于Java的新语言。
  • AspectWerkz:AOP框架,使用字节码动态编织器和XML配置。
  • JBoss-AOP:基于拦截器和元数据的AOP框架,运行在JBoss应用服务器上。

AOP中使用的一些实现技术有:

  • BCEL:Byte-Code Engineering Library,Java字节码操作类库。
  • CGLIB:CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。
  • Javassist:Java字节码操作类库,JBoss的一个子项目。

面向方面编程(AOP)的常用术语

  • 切面Aspect: Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些切入点Pointcut 以及对切入点进行相应的操作的通知Advice。
  • 连接点Joint point:表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它连接点jointpoint。
  • 切入点Pointcut:表示一组连接点jointpoint,这些连接点或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的操作处理通知Advice将要发生的地方。
  • 通知Advice:Advice 定义了在切入点pointcut 里面定义的程序点具体要做的操作和处理,它通过 before、after 和 around 来区别是在每个切入点之前、之后还是代替执行的代码。
  • 目标对象Target:代理的目标对象,即切面要被应用到的目标对象。
  • 织入Weave:指将切面应用到目标对象,并导致代理对象创建的过程。

二、Advice通知

Advice通知是AOP联盟定义的一个接口,定义当拦截到连接点做相应的处理操作,为切面增强提供织入接口。在spring AOP中,通知主要描述Spring AOP围绕方法调用而注入切面的行为,Spring AOP的通知扩展了AOP联盟的通知接口,提供了前置通知 BeforeAdvice、后置通知 AfterReturningAdvice 、最终通知 AfterAdvice 和例外通知 ThrowsAdvice等。

AOP联盟定义的接口都只是类似一个标记,的接口方法还是要视具体的连接点的情况而定,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface Advice {

}

public interface BeforeAdvice extends Advice {

}

public interface AfterAdvice extends Advice {

}

public interface ThrowsAdvice extends AfterAdvice {

}

可以看到,都只有接口,而没有具体方法的定义,而真正要实现的方法都在具体的连接器指定后才有,如下所示:

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
// 前置增强接口,使用这个前置接口需要实现一个回调函数
public interface MethodBeforeAdvice extends BeforeAdvice {

/**
* 作为回调函数,该方法的实现在Advice中被配置到目标方法后,会调用目标方法时被回调
* @param method Method对象,是目标方法的反射对象
* @param args 对象数组,包含目标方法的输入参数
* @param target
* @throws Throwable
*/
void before(Method method, Object[] args, Object target) throws Throwable;

}

public interface AfterReturningAdvice extends AfterAdvice {
/**
* 作为回调函数,该方法的实现在Advice中被配置到目标方法返回后,对目标返回结果进行增强
* @param returnValue 返回值
* @param method Method对象,是目标方法的反射对象
* @param args 对象数组,包含目标方法的输入参数
* @param target
* @throws Throwable
*/
void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;

}

三、Pointcut切点

Pointcut切入点决定通知Advice应该作用于哪个连接点,即通过Pointcut切入点来定义目标对象中需要使用AOP增强的方法集合,这些集合的选取可以按照一定的规则来完成。

Pointcut通常意味着标识方法,这些需要增强的方法可以被某个正则表达式进行标识,或者根据指定方法名进行匹配等。下面是Pointcut设计:

Pointcut

Pointcut切入点源码

1
2
3
4
5
6
7
8
9
10
public interface Pointcut {  
//获取类过滤器
ClassFilter getClassFilter();

//获取匹配切入点的方法
MethodMatcher getMethodMatcher();

//总匹配的标准切入点实例
Pointcut TRUE = TruePointcut.INSTANCE;
}

在Pointcut的基本接口定义中可以看到,需要返回一个MethodMatcher。对于Point的匹配判断功能,具体是由这个返回的MethodMatcher来完成的,也就是说,由这个MethodMatcher来判断是否需要对当前方法调用进行增强,或者是否需要对当前调用方法应用配置好Advice通知。

实现原理

在Pointcut的类继承关系中,以正则表达式切点JdkRegexpMethodPointcut的实现原理为例,来具体了解切点Pointcut的工作原理。该类完成通过正则表达式对方法名进行匹配的功能。

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
public class JdkRegexpMethodPointcut extends AbstractRegexpMethodPointcut {  
//要编译的正则表达式模式
private Pattern[] compiledPatterns = new Pattern[0];
//编译时要排除的正则表达式模式
private Pattern[] compiledExclusionPatterns = new Pattern[0];

//将给定的模式字符串数组初始化为编译的正则表达式模式
protected void initPatternRepresentation(String[] patterns) throws PatternSyntaxException {
this.compiledPatterns = compilePatterns(patterns);
}

//将给定的模式字符串数组初始化为编译时要排除的正则表达式模式
protected void initExcludedPatternRepresentation(String[] excludedPatterns) throws PatternSyntaxException {
this.compiledExclusionPatterns = compilePatterns(excludedPatterns);
}

//使用正则表达式匹配给定的名称
protected boolean matches(String pattern, int patternIndex) {
Matcher matcher = this.compiledPatterns[patternIndex].matcher(pattern);
return matcher.matches();
}

//使用要排除的正则表达式匹配给定的名称
protected boolean matchesExclusion(String candidate, int patternIndex) {
Matcher matcher = this.compiledExclusionPatterns[patternIndex].matcher(candidate);
return matcher.matches();
}

//将给定的字符串数组编译为正则表达的模式
private Pattern[] compilePatterns(String[] source) throws PatternSyntaxException {
Pattern[] destination = new Pattern[source.length];
for (int i = 0; i < source.length; i++) {
destination[i] = Pattern.compile(source[i]);
}
return destination;
}
}

从上面的源码分析中,我们可以看到,最简单的使用正则表达式匹配的Pointcut切入点基本功能就是根据正则表达式判断方法名等是否匹配。

四、Advisor通知器:

当完成对目标对象方法的增强行为操作 Advice 和切入点 Point 的设计开发之后,需要一个对象将目标对象、增强行为和切入点三者结合起来,通知器Advisor就是一个实现这个功能的对象,即通过Advisor通知器,可以定义那些目标对象的那些方法在什么地方使用这些增加的行为。

Advisor通知器定义

Advisor通知器的源码如下:

1
2
3
4
5
6
7
public interface Advisor {  
//获取切面的通知Advice
Advice getAdvice();

//判断这个通知是否和某个特定的实例对象相关
boolean isPerInstance();
}

实现原理

查看Advisor通知器的继承体系,发现Advisor的实现类很多,我们以最常用的DefaultPointcutAdvisor为例,分析通知器的工作原理。

  1. DefaultPointcutAdvisor源码如下

    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
    public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {  
    //默认切入点 — - - ——>
    //Pointcut.TRUE在切入点中的定义为:Pointcut TRUE = TruePointcut.INSTANCE;
    private Pointcut pointcut = Pointcut.TRUE;

    //无参构造方法,创建一个空的通知器
    public DefaultPointcutAdvisor() {
    }

    //创建一个匹配所有方法的通知器
    public DefaultPointcutAdvisor(Advice advice) {
    this(Pointcut.TRUE, advice);
    }

    //创建一个指定切入点和通知的通知器
    public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
    this.pointcut = pointcut;
    setAdvice(advice);
    }

    //为通知设置切入点
    public void setPointcut(Pointcut pointcut) {
    this.pointcut = (pointcut != null ? pointcut : Pointcut.TRUE);
    }

    //获取切入点
    public Pointcut getPointcut() {
    return this.pointcut;
    }
    public String toString() {
    return getClass().getName() + ": pointcut [" + getPointcut() + "]; advice [" + getAdvice() + "]";
    }
    }
  1. 其中默认切入点为TruePointcut,主要功能是配置默认的类过滤器和方法匹配器,即定义Spring AOP对于哪些类的哪些方法其作用,源码如下:

    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
    class TruePointcut implements Pointcut, Serializable {  
    //INSTANCE是TruePointcut类的一个常量单件,即整个应用中只有这个一个,
    //不会创建第二个实例对象,确保该实例对象的唯一性,单例模型
    public static final TruePointcut INSTANCE = new TruePointcut();

    //单态模式构造方法
    private TruePointcut() {
    }

    //获取切入点的类过滤器
    public ClassFilter getClassFilter() {
    return ClassFilter.TRUE;
    }
    //获取切入点的方法匹配器
    public MethodMatcher getMethodMatcher() {
    return MethodMatcher.TRUE; // ————>
    }

    //获取单态模式对象的方法
    private Object readResolve() {
    return INSTANCE;
    }
    public String toString() {
    return "Pointcut.TRUE";
    }
    }

    //———————————————
    public interface MethodMatcher {

    boolean matches(Method method, Class<?> targetClass);

    boolean isRuntime();

    boolean matches(Method method, Class<?> targetClass, Object... args);

    MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;

    }

    从TruePointcut的源码我们看到,切入点使用TrueClassFilter作为类过滤器,匹配任意的类; 使用TrueMethodMatcher作为方法匹配器,匹配任意的方法。下面我们继续分析TrueClassFilter类过滤器和TrueMethodMatcher方法匹配器。

  2. TrueClassFilter作为默认切入点的默认类过滤器,主要告诉切入点对哪些类进行增强,源码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class TrueClassFilter implements ClassFilter, Serializable {  
    //单态模式
    public static final TrueClassFilter INSTANCE = new TrueClassFilter();

    //单态模式的构造方法
    private TrueClassFilter() {
    }

    //切入点过滤匹配类的方法,默认对所有的类都增强
    public boolean matches(Class clazz) {
    return true;
    }

    //获取单态模式对象的方法
    private Object readResolve() {
    return INSTANCE;
    }
    public String toString() {
    return "ClassFilter.TRUE";
    }
    }
  1. TrueMethodMatcher作为默认切入点的默认方法匹配器,主要告诉切入点对哪些方法进行增强,源码如下:

    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
    class TrueMethodMatcher implements MethodMatcher, Serializable {  
    //单态模式
    public static final TrueMethodMatcher INSTANCE = new TrueMethodMatcher();
    //单态模式构造方法
    private TrueMethodMatcher() {
    }

    //不支持运行时调用
    public boolean isRuntime() {
    return false;
    }

    //切入点匹配方法时调用,默认匹配所有的方法
    public boolean matches(Method method, Class targetClass) {
    return true;
    }

    //运行时调用将抛出异常
    public boolean matches(Method method, Class targetClass, Object[] args) {
    throw new UnsupportedOperationException();
    }

    //获取单态模式对象的方法
    private Object readResolve() {
    return INSTANCE;
    }
    public String toString() {
    return "MethodMatcher.TRUE";
    }
    }

    从上面方法匹配器的源码,我们可以看出,切入点对方法进行匹配时不支持运行时的匹配,如果在运行时进行匹配将抛出异常。