共计 2391 个字符,预计需要花费 6 分钟才能阅读完成。
AOP 是 Aspect Oriented Programming,即面向切面编程。
那什么是 AOP?
我们先回顾一下 OOP:Object Oriented Programming,OOP 作为面向对象编程的模式,获得了巨大的成功,OOP 的主要功能是数据封装、继承和多态。
而 AOP 是一种新的编程方式,它和 OOP 不同,OOP 把系统看作多个对象的交互,AOP 把系统分解为不同的关注点,或者称之为切面(Aspect)。
要理解 AOP 的概念,我们先用 OOP 举例,比如一个业务组件BookService
,它有几个业务方法:
- createBook:添加新的 Book;
- updateBook:修改 Book;
- deleteBook:删除 Book。
对每个业务方法,例如,createBook()
,除了业务逻辑,还需要安全检查、日志记录和事务处理,它的代码像这样:
public class BookService {public void createBook(Book book) {securityCheck();
Transaction tx = startTransaction();
try {// 核心业务逻辑
tx.commit();} catch (RuntimeException e) {tx.rollback();
throw e;
}
log("created book:" + book);
}
}
继续编写updateBook()
,代码如下:
public class BookService {public void updateBook(Book book) {securityCheck();
Transaction tx = startTransaction();
try {// 核心业务逻辑
tx.commit();} catch (RuntimeException e) {tx.rollback();
throw e;
}
log("updated book:" + book);
}
}
对于安全检查、日志、事务等代码,它们会重复出现在每个业务方法中。使用 OOP,我们很难将这些四处分散的代码模块化。
考察业务模型可以发现,BookService
关心的是自身的核心逻辑,但整个系统还要求关注安全检查、日志、事务等功能,这些功能实际上“横跨”多个业务方法,为了实现这些功能,不得不在每个业务方法上重复编写代码。
一种可行的方式是使用 Proxy 模式,将某个功能,例如,权限检查,放入 Proxy 中:
public class SecurityCheckBookService implements BookService {private final BookService target;
public SecurityCheckBookService(BookService target) {this.target = target;
}
public void createBook(Book book) {securityCheck();
target.createBook(book);
}
public void updateBook(Book book) {securityCheck();
target.updateBook(book);
}
public void deleteBook(Book book) {securityCheck();
target.deleteBook(book);
}
private void securityCheck() {...}
}
这种方式的缺点是比较麻烦,必须先抽取接口,然后,针对每个方法实现 Proxy。
另一种方法是,既然 SecurityCheckBookService
的代码都是标准的 Proxy 样板代码,不如把权限检查视作一种切面(Aspect),把日志、事务也视为切面,然后,以某种自动化的方式,把切面织入到核心逻辑中,实现 Proxy 模式。
如果我们以 AOP 的视角来编写上述业务,可以依次实现:
- 核心逻辑,即 BookService;
- 切面逻辑,即:
- 权限检查的 Aspect;
- 日志的 Aspect;
- 事务的 Aspect。
然后,以某种方式,让框架来把上述 3 个 Aspect 以 Proxy 的方式“织入”到 BookService
中,这样一来,就不必编写复杂而冗长的 Proxy 模式。
AOP 原理
如何把切面织入到核心逻辑中?这正是 AOP 需要解决的问题。换句话说,如果客户端获得了 BookService
的引用,当调用 bookService.createBook()
时,如何对调用方法进行拦截,并在拦截前后进行安全检查、日志、事务等处理,就相当于完成了所有业务功能。
在 Java 平台上,对于 AOP 的织入,有 3 种方式:
- 编译期:在编译时,由编译器把切面调用编译进字节码,这种方式需要定义新的关键字并扩展编译器,AspectJ 就扩展了 Java 编译器,使用关键字 aspect 来实现织入;
- 类加载器:在目标类被装载到 JVM 时,通过一个特殊的类加载器,对目标类的字节码重新“增强”;
- 运行期:目标对象和切面都是普通 Java 类,通过 JVM 的动态代理功能或者第三方库实现运行期动态织入。
最简单的方式是第三种,Spring 的 AOP 实现就是基于 JVM 的动态代理。由于 JVM 的动态代理要求必须实现接口,如果一个普通类没有业务接口,就需要通过 CGLIB 或者 Javassist 这些第三方库实现。
AOP 技术看上去比较神秘,但实际上,它本质就是一个动态代理,让我们把一些常用功能如权限检查、日志、事务等,从每个业务方法中剥离出来。
需要特别指出的是,AOP 对于解决特定问题,例如事务管理非常有用,这是因为分散在各处的事务代码几乎是完全相同的,并且它们需要的参数(JDBC 的 Connection)也是固定的。另一些特定问题,如日志,就不那么容易实现,因为日志虽然简单,但打印日志的时候,经常需要捕获局部变量,如果使用 AOP 实现日志,我们只能输出固定格式的日志,因此,使用 AOP 时,必须适合特定的场景。