使用AOP

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(),除了业务逻辑,还需要安全检查、日志记录和事务处理,它的代码像这样:

  1. public class BookService {
  2. public void createBook(Book book) {
  3. securityCheck();
  4. Transaction tx = startTransaction();
  5. try {
  6. // 核心业务逻辑
  7. tx.commit();
  8. } catch (RuntimeException e) {
  9. tx.rollback();
  10. throw e;
  11. }
  12. log("created book: " + book);
  13. }
  14. }

继续编写updateBook(),代码如下:

  1. public class BookService {
  2. public void updateBook(Book book) {
  3. securityCheck();
  4. Transaction tx = startTransaction();
  5. try {
  6. // 核心业务逻辑
  7. tx.commit();
  8. } catch (RuntimeException e) {
  9. tx.rollback();
  10. throw e;
  11. }
  12. log("updated book: " + book);
  13. }
  14. }

对于安全检查、日志、事务等代码,它们会重复出现在每个业务方法中。使用OOP,我们很难将这些四处分散的代码模块化。

考察业务模型可以发现,BookService关系的是自身的核心逻辑,但整个系统还要求关注安全检查、日志、事务等功能,这些功能实际上“横跨”多个业务方法,为了实现这些功能,不得不在每个业务方法上重复编写代码。

一种可行的方式是使用Proxy模式,将某个功能,例如,权限检查,放入Proxy中:

  1. public class SecurityCheckBookService implements BookService {
  2. private final BookService target;
  3. public SecurityCheckBookService(BookService target) {
  4. this.target = target;
  5. }
  6. public void createBook(Book book) {
  7. securityCheck();
  8. target.createBook(book);
  9. }
  10. public void updateBook(Book book) {
  11. securityCheck();
  12. target.updateBook(book);
  13. }
  14. public void deleteBook(Book book) {
  15. securityCheck();
  16. target.deleteBook(book);
  17. }
  18. private void securityCheck() {
  19. ...
  20. }
  21. }

这种方式的缺点是比较麻烦,必须先抽取接口,然后,针对每个方法实现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时,必须适合特定的场景。

读后有收获可以支付宝请作者喝咖啡,读后有疑问请加微信群讨论:

使用AOP - 图1 使用AOP - 图2