文章目录
概念
面向切面编程 (AOP)
横切关注点
分布于应用中不同流程的多处相似的功能被称为横切关注点(cross-cutting concerns). 通常这些横切关注点从概念上是与应用的核心业务逻辑相分离的。将这些横切关注点与核心业务逻辑相分离,这就是面向切面编程(AOP)所要解决的问题
依赖注入有助于应用对象之间的解耦,而AOP可以实现横切关注点与它们所影响的对象之间的解耦
常见横切关注点(场景): 参数校验: 参数是否合法, 日志: 入参出参耗时等, 安全: 权限校验, 等
切面
在使用面向切面编程时,我们可以在一个地方定义通用的功能,通过声明式的方式来定义这些功能以何种方式在何处应用,而无需修改受影响的类。这些横切关注点可以被模块化为一个特殊的类,这些类被称为切面
比如参数校验的 VerifyAspect, 处理日志的切面类: LogAspect, 处理权限的 SecurityAspect 等, 里面配置了切点通知等信息
切点(Poincut)
定义适用切面的位置, 即哪些方法可以被某个切面增强
有些AOP框架允许我们创建动态的切点,可以根据运行时的决策(比如方法的参数值)来决定是否应用通知
通知(Advice)
在某个连接点上执行的动作. 定义了某个方法该被如何增强
- Before:在方法被调用之前调用通知
- After:在方法完成之后被调用通知,无论方法执行是否成功
- AfterReturning:在方法成功执行之后调用通知
- AfterThrowing:在方法抛出异常后调用通知
- Around:通知包裹了被通知的方法,在被通知方法调用之前和调用之后执行自定义的行为
许多AOP框架, 包括Spring, 都是以拦截器做通知模型, 并维护着一个以拦截点为中心的拦截器链
顾问(Advisor)
是 Advice 的包装体现, 内部封装了一个 Advice 和 PointCut
连接点(Joinpoint)
在系统运行之前,AOP的功能模块需要织入到OOP的功能模块中。所以,要进行这种织入过程我们需要知道在系统的哪些执行点上进行织入操作,这些将要在其之上进行织入操作的系统执行点就称为Joinpoint。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程中,并添加新的行为. 通常都是指被增强的方法
目标对象(Target)
将要被增强的对象, 包含主业务逻辑的类的对象
织入(weaving)
织入是将切面应用到目标对象来创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入
- 编译期——切面在目标类编译时被织入。AspectJ就是以这种方式织入切面的
- 类加载期——切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的LTW(load-time weaving)就支持以这种方式织入切面
- 运行期——切面在应用运行的某个时刻被织入,一般情况下,在织入切面时,AOP容器会为目标对象动态创建一个代理对象。Spring AOP就是以这种方式织入切面的
引入(Introduction)
引入允许我们向现有的类添加新方法或者属性,可以实现无需修改这些现有的类的情况下,让它们具有新的行为和状态
AspectJ 和 Spring AOP
Spring Aop
它基于动态代理来实现。默认地,如果使用接口的,用 JDK 提供的动态代理实现,如果没有接口,使用 CGLIB 实现。
Spring 3.2 以后,spring-core 直接就把 CGLIB 和 ASM 的源码包括进来了,这也是为什么我们不需要显式引入这两个依赖
Spring 的 IOC 容器和 AOP 都很重要,Spring AOP 需要依赖于 IOC 容器来管理。
Spring AOP 只能作用于 Spring 容器中的 Bean,它是使用纯粹的 Java 代码实现的,只能作用于 bean 的方法。
Spring 提供了 AspectJ 的支持,但只用到的AspectJ的切点解析和匹配。 Spring 延用了 AspectJ 中的概念,包括使用了 AspectJ 提供的 jar 包中的注解,但是不依赖于其实现功能。如 @Aspect、@Pointcut、@Before、@After 等注解都是来自于 AspectJ,但是功能的实现是纯 Spring AOP 自己实现的。
很多人会对比 Spring AOP 和 AspectJ 的性能,Spring AOP 是基于代理实现的,在容器启动的时候需要生成代理实例,在方法调用上也会增加栈的深度,使得 Spring AOP 的性能不如 AspectJ 那么好。
AspectJ
AspectJ 出身也是名门,来自于 Eclipse 基金会,link:https://www.eclipse.org/aspectj
属于静态织入,它是通过修改代码来实现的,它的织入时机可以是:
- Compile-time weaving:编译期织入,如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。
- Post-compile weaving:编译后织入,也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。
- Load-time weaving:指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法。1、自定义类加载器来干这个,这个应该是最容易想到的办法,在被织入类加载到 JVM 前去对它进行加载,这样就可以在加载的时候定义行为了。2、在 JVM 启动的时候指定
AspectJ 提供的 agent:-javaagent:xxx/xxx/aspectjweaver.jar。
AspectJ 能干很多 Spring AOP 干不了的事情,它是 AOP 编程的完全解决方案。Spring AOP 致力于解决的是企业级开发中最普遍的 AOP 需求(方法织入),而不是力求成为一个像 AspectJ 一样的 AOP 编程完全解决方案。
因为 AspectJ 在实际代码运行前完成了织入,所以大家会说它生成的类是没有额外运行时开销的。