AOP横扫千军-概念理解
在了解AOP之前,我们先来总览一下Spring生命之树。
Spring的生命之树
前面几章节我们主要对IoC容器进行了研究,这里先回顾一下。
- IoC容器就像是通灵之术,我们可以在需要的时候,将对象召唤出来。
- 在召唤之前,我们要创建并将对象放入容器,处理各种依赖绑定关系。 这个工作可以通过BeanFactory和ApplicationContext来做。
- ApplicationContext实现了BeanFactory,还有统一资源管理、国际化、事件发布等功能。
有了以上大树干的知识后,我们继续往上探索。这次,我们来到了Spring AOP分支。
0x01 为什么要有AOP
软件开发一直在寻求更加高效、更易维护、更易扩展的方式。为了提交开发效率,我们不断对语言进行了抽象。汇编是对机器语言二进制的抽象,C语言是对汇编语言的抽象封装。为了便于维护和扩展,我们就对某些相同的功能进行归类和模块化,走过了从过程化编程到面向对象编程OOP(Object-Oriented Programming)的“短暂而漫长”的历程。
OOP擅长的是将业务进行抽象,然后封装、模块化。但是有一些场景使用OOP去处理就显得非常吃力。例如,散落在各个模块的日志记录、安全检查、异常处理、事务管理等。这些相同的功能,如果分散在各个模块,那维护起来简直是噩梦。
加入各种横切需求后的系统模块关系图我们需要找到能够简洁处理以上这些问题的编程思想,于是AOP(Aspect-Oriendted Programming)就呼之欲出了。
0x02 AOP走向现实的方案
上面说的只是AOP的由来以及概念。有了概念之后,我们还需要让AOP走向现实,在开发的时候能够用上。在java编程体系上,有两种方法可以实现AOP功能:
- 静态AOP(一代),在编译时,将切面信息写到类中。
- 动态AOP(二代),在系统运行、类加载期间,对字节码进行操作,来织入切面信息。
两种方式各有利弊,静态方式性能好但不灵活,动态方式灵活但有损性能。随着硬件资源的提升,性能的问题得到解决,灵活变得越来越重要。
所有的Java程序的class都要通过相应的类加载器加载到Java虚拟机之后,才可以运行。默认的类加载器会读取class字节码文件,然后按照class字节码规范,解析并加载这些class文件到虚拟机运行。如果我们能够在这个class文件加载到虚拟机运行期间,将横切逻辑织入到class文件的话,就完成了AOP和OOP的融合。AspectJ项目框架就是采用这种方式实现的。
0x03 AOP国家的公民--学习AOP必须掌握的概念
Joinpoint:织入点
在系统运行之前,AOP功能模块都需要织入到OOP功能模块中。这些将要在其之上进行织入操作的系统执行点就称为Joinpoint。
Joinpoint是一个描述织入点的一个概念,方便表达沟通,不会在程序中体现。基本上,程序执行过程中你认为必要的执行时点都可以作为Joinpont。常见的Joinpoint有方法调用、构造方法调用、字段设置获取、异常处理执行、类初始化等。
Pointcut -- Joinpoint的表达方式
Pointcut 是 Joinpoint的表达方式,表示织入点的具体位置,通过Pointcut 我们才知道往哪些织入点织入横切逻辑。
Pointcut 的表述方式很重要,需要在程序里面体现的:
- 1 直接指定Joinpoint所在方法名。
- 2 使用正则表达式。
- 3 使用特定的Pointcut 表述语言。
- 4 Pointcut的逻辑运算&& 和 || 。
举栗子:
1)execution(* (..))
表示匹配所有方法
2)execution(public * com. savage.service.UserService.(..))
表示匹配com.savage.server.UserService中所有的公有方法
3)execution(* com.savage.server...(..))
表示匹配com.savage.server包及其子包下的所有方法
除了execution表示式外,还有within、this、target、args等Pointcut表示式。一个Pointcut定义由Pointcut表示式和Pointcut签名组成
@Pointcut("execution(* com.savage.aop.MessageSender.*(..))")
private void logSender(){
}
@Pointcut("execution(* com.savage.aop.MessageReceiver.*(..))")
private void logReceiver(){}
@Pointcut("logSender() || logReceiver()")
private void logMessage(){}
Advice
Advice是单一横切关注点逻辑的载体,它代表将要织入到Joinpoint的横切逻辑,相当于class中的method。也就是说,我们要实现的逻辑代码是放在Advice中去实现的。
Advice可以分为多种具体的形式:
Before Advice: 在Joinpoint指定位置之前执行横切逻辑。
After Advice: 在Joinpoint指定位置之后执行横切逻辑。
Around Advice: 包裹了Joinpoint指定位置的前后,最常用的Advice。通常叫做拦截器(Interceptor)。
Introduction Advice: 可以为原有对象添加新的特性或者行为。
Aspect
Aspect是对系统中横切关注点逻辑进行模块化封装的AOP概念实体,相当于OOP的class。通常情况下,Aspect可以包含多个Pointcut和相关的Advice定义。
下面来一个简单的示例,结合上面讲的概念来理解一下切面:
package com.sxit;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect //Aspect是对系统中横切关注点逻辑进行模块化封装的AOP概念实体,相当于OOP的class
public class AspectStyle {
// Pointcut表示织入点的具体位置,通过Pointcut 我们才知道往哪些织入点织入横切逻辑
@Pointcut("execution(* com.sxit..*.*(..))")
public void init(){
}
//Advice是单一横切关注点逻辑的载体,它代表将要织入到Joinpoint的横切逻辑
@Before(value="init()")
public void before(){
System.out.println("方法执行前执行.....");
}
@AfterReturning(value="init()")
public void afterReturning(){
System.out.println("方法执行完执行.....");
}
@AfterThrowing(value="init()")
public void throwss(){
System.out.println("方法异常时执行.....");
}
@After(value="init()")
public void after(){
System.out.println("方法最后执行.....");
}
@Around(value="init()")
public Object around(ProceedingJoinPoint pjp){
System.out.println("方法环绕start.....");
Object o = null;
try {
o = pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("方法环绕end.....");
return o;
}
}
0x04 织入和织入器
上面描述的都是AOP的概念,是独立于OOP编程模型的。只有经过织入过程之后,以Aspect模块化的横切关注点才会集成到OOP现存系统中。而完成织入过程的那个“人”就称为织入器(Weaver)啦。ProxyFactory类是Spring AOP中最常用的织入器。
AOP织入小结
本文主要介绍了AOP的概念,先对AOP的世界来一个整体的理解。在真正的使用过程中,不至于迷茫。