# 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);
}
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;
}
}
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) | 把切面应用到目标对象的过程。 |
也就是说,我们需要编写一个类,这个类用来处理一些非业务的功能,例如打印日志,这个类也就是切面。在这类中,肯定要做一些工作,例如打印日志,这就是通知。而在哪个地方执行通知呢,可以插入的位置很多,也就是连接点很多,于是在指定的连接点进行插入,这就是切入点。
而对于通知,有多个类型:
前置通知:在目标方法调用之前执行;
后置通知:在目标方法执行完成后执行 (无论是否抛出异常);
返回通知:仅在目标方法成功返回后执行 (无异常抛出);
异常通知:仅在目标方法抛出异常后执行;
环绕通知:完全控制目标方法的执行 (方法调用前后均可干预)。
这里还有一个概念就是 AspectJ
, AspectJ
是一个 Java 开发的 AOP 框架,是由 Xerox PARC(施乐帕洛阿尔托研究中心)的一个研究团队开发的,后来捐给了 Eclipse 基金会。Spring 本来只支持基于 XML 配置的 AOP,后来觉得太麻烦了,就借用了AspectJ 的注解,也就是是说使用基于注解配置的 Spring AOP,需要引入 spring-aspects
,而这个包里依赖了 AspectJ 的注解和工具类,但底层实现是 Spring 自己的动态代理,但没有用 AspectJ 的编译期或类加载期织入器。
一句话总结:Spring AOP 注解风格 = Spring 动态代理 + AspectJ 注解语法。
下面就来看一下使用 Spring 如何实现 AOP 编程。
还是两种方式:基于 XML 和 基于注解 方式,但是基于 XML 方式确实很少用了,了解一下吧。