面向切面编程(AOP)
首先 AOP 是一种编程方式
为了理解AOP的概念,我们先用OOP举例,比如一个业务组件BookService
,它有几个业务方法:
- createBook:添加新的Book;
- updateBook:修改Book;
- deleteBook:删除Book;
对每个业务方法,例如,createBook()
,除了业务逻辑,还需要安全检查、日志记录和事务处理,它的代码像这样:
1 | public class BookService { |
继续编写updateBook()
,代码如下:
1 | public class BookService { |
对于安全检查、日志、事务等代码,它们会重复出现在每个业务方法中。使用OOP,我们很难将这些四处分散的代码模块化。
考察业务模型可以发现,BookService
关心的是自身的核心逻辑,但整个系统还要求关注安全检查、日志、事务等功能,这些功能实际上“横跨”多个业务方法,为了实现这些功能,不得不在每个业务方法上重复编写代码。
对于这种问题我们可以使用鉴权中间件的方式,统一检查权限。但是日志和 try catch 这就没法用中间件解决了。
另一种可行的方式是使用Proxy模式,将某个功能,例如,权限检查,放入Proxy中:
1 | public class SecurityCheckBookService implements BookService { |
也就是先把核心代码写出来,然后再抽象出一个代理执行的类,对原先的类都加上统一的处理。这样做就很麻烦,原来一个类的事现在要先设计方法,然后拆分成 2 个类来写。但是其实每个方法要做的鉴权还是都要再写一遍。
然后我们就会想,反正鉴权,日志,错误处理这些事每个方法每个类都要做的,那不如把这些抽离出来,单独编写,然后再代码编译的时候,再自动的加进去,不需要程序员一个个写。::这就是面向切面编程了。::
既然
SecurityCheckBookService
的代码都是标准的Proxy样板代码,不如把权限检查视作一种切面(Aspect),把日志、事务也视为切面,然后,以某种自动化的方式,把切面织入到核心逻辑中,实现Proxy模式。
如果我们以AOP的视角来编写上述业务,可以依次实现:
- 核心逻辑,即BookService;
- 切面逻辑,即:
- 权限检查的Aspect;
- 日志的Aspect;
- 事务的Aspect。
然后,以某种方式,让框架来把上述3个Aspect以Proxy的方式“织入”到BookService
中,这样一来,就不必编写复杂而冗长的Proxy模式。这个植入的过程当然是用代码自动化的方式实现,不需要程序员去写,也不会表现在核心逻辑的代码里。
如何把切面织入到核心逻辑中?这正是AOP需要解决的问题。换句话说,如果客户端获得了BookService
的引用,当调用bookService.createBook()
时,如何对调用方法进行拦截,并在拦截前后进行安全检查、日志、事务等处理,就相当于完成了所有业务功能。
在Java平台上,对于AOP的织入,有3种方式:
- 编译期:在编译时,由编译器把切面调用编译进字节码,这种方式需要定义新的关键字并扩展编译器,AspectJ就扩展了Java编译器,使用关键字aspect来实现织入;
- 类加载器:在目标类被装载到JVM时,通过一个特殊的类加载器,对目标类的字节码重新“增强”;
- 运行期:目标对象和切面都是普通Java类,通过JVM的动态代理功能或者第三方库实现运行期动态织入。
最简单的方式是第三种,Spring的AOP实现就是基于JVM的动态代理。由于JVM的动态代理要求必须实现接口,如果一个普通类没有业务接口,就需要通过CGLIB或者Javassist这些第三方库实现。