# Spring教程 - 6 面向切面编程AOP

# 6.1 遇到的问题

在传统的面向对象编程(OOP)中,我们把程序分解成对象和类,每个类负责自己的功能。但是有些功能并不是单独属于某个业务类的,而是跨越多个类都会用到的,比如:

  • 日志记录
  • 权限检查
  • 事务管理
  • 性能统计
  • 等等。

这些功能多个业务类都会使用,如果将这些代码散布在所有的业务类李,会造成代码重复,不易维护。


举个栗子:

我们有一个用于计算的 Service,首先定义接口:

ICalculatorService.java

package com.foooor.hellospring.service;

public interface ICalculatorService {
    /**
     * 加法
     */
    int add(int a, int b);
    
    /**
     * 减法
     */
    int sub(int a, int b);
    
    /**
     * 乘法
     */
    int mul(int a, int b);
    
    /**
     * 除法
     */
    int div(int a, int b);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

然后定义实现类:

CalculatorServiceImpl.java

package com.foooor.hellospring.service.impl;

import com.foooor.hellospring.service.ICalculatorService;
import org.springframework.stereotype.Service;

@Service
public class CalculatorServiceImpl implements ICalculatorService {

    /**
     * 加法
     */
    public int add(int a, int b) {
        System.out.println("add a=" + a + ", b=" + b);
        int result = a + b;
        System.out.println("add result:" + result);
        return result;
    }

    /**
     * 减法
     */
    public int sub(int a, int b) {
        System.out.println("sub a=" + a + ", b=" + b);
        int result = a - b;
        System.out.println("sub result:" + result);
        return result;
    }

    /**
     * 乘法
     */
    public int mul(int a, int b) {
        System.out.println("mul a=" + a + ", b=" + b);
        int result = a * b;
        System.out.println("mul result:" + result);
        return result;
    }

    /**
     * 除法
     */
    public int div(int a, int b) {
        System.out.println("div a=" + a + ", b=" + b);
        int result = a / b;
        System.out.println("div result:" + result);
        return result;
    }
}
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
40
41
42
43
44
45
46
47
48

可以看到在上面的代码中,所有的方法中都有相似的日志记录,其实这并不属于业务功能,却和业务功能混合在一起了。

我们能否将这些附加的功能,例如上面的日志记录,抽取出来,统一管理呢?

可以!AOP 来了。


# 6.2 AOP简介

在讲解 AOP 之前,先看一个设计模式,在 Java 教程中已经讲解过了,如果你没有看,先跳转过去看一下。

传送门 --> 代理模式 (opens new window)

AOP(Aspect-Oriented Programming),即面向切面编程,它是一种编程范式,不是一个新的技术。AOP 可以将可重用的模块,动态的切入到需要的地方。AOP 底层就是通过动态代理实现的,通过代理对象,在调用目标对象之前或之后,执行一些额外的操作。

对于 Spring 而言,实现接口就使用 JDK动态代理,如果不实现接口就使用 CGLib动态代理。

在 AOP 中,设计到一下几个概念:

概念 解释
横切关注点(Cross-cutting Concern) 横切关注点就是那些与业务无关、却需要在很多地方重复出现的功能。例如日志记录、权限检查。
AOP 的价值就是把它们抽出来统一管理,避免“横切”散落在业务代码中。
切面(Aspect) 一个模块化的横切关注点,例如日志切面、事务切面。
也就是创建一个类,用来处理具体的横切关注点功能,例如日志记录。
连接点(Join point) 程序执行过程中可以插入切面的位置,比如方法调用、异常抛出。
也就是在哪些地方可以插入切面,切面也不是任何地方都能插入的,一般可以在方法调用前后,方法抛异常时等位置插入切面进行执行。
通知(Advice) 通知就是在连接点具体要执行的动作,就是具体要做什么,例如打印日志。
切入点(Pointcut) 匹配连接点的表达式,用来确定通知应该在哪些方法上执行。
连接点表示的是所有能插入的位置,而切入点是你想在哪些连接点插入。
通知是指要干什么,切入点是指在哪干,切面是把“干什么 + 在哪干”打包成一个模块
目标对象(Target Object) 被切面增强的对象(业务对象)。
织入(Weaving) 把切面应用到目标对象的过程。

也就是说,我们需要编写一个类,这个类用来处理一些非业务的功能,例如打印日志,这个类也就是切面。在这类中,肯定要做一些工作,例如打印日志,这就是通知。而在哪个地方执行通知呢,可以插入的位置很多,也就是连接点很多,于是在指定的连接点进行插入,这就是切入点。

而对于通知,有多个类型:

  • 前置通知:在目标方法调用之前执行;

  • 后置通知:在目标方法执行完成后执行 (无论是否抛出异常);

  • 返回通知:仅在目标方法成功返回后执行 (无异常抛出);

  • 异常通知:仅在目标方法抛出异常后执行;

  • 环绕通知:完全控制目标方法的执行 (方法调用前后均可干预)。


这里还有一个概念就是 AspectJAspectJ 是一个 Java 开发的 AOP 框架,是由 Xerox PARC(施乐帕洛阿尔托研究中心)的一个研究团队开发的,后来捐给了 Eclipse 基金会。Spring 本来只支持基于 XML 配置的 AOP,后来觉得太麻烦了,就借用了AspectJ 的注解,也就是是说使用基于注解配置的 Spring AOP,需要引入 spring-aspects,而这个包里依赖了 AspectJ 的注解和工具类,但底层实现是 Spring 自己的动态代理,但没有用 AspectJ 的编译期或类加载期织入器。

一句话总结:Spring AOP 注解风格 = Spring 动态代理 + AspectJ 注解语法


下面就来看一下使用 Spring 如何实现 AOP 编程。

还是两种方式:基于 XML 和 基于注解 方式,但是基于 XML 方式确实很少用了,了解一下吧。