SpringFramework — AOP 基础

SpringFramework — AOP 基础

小龙 544 2022-06-07

AOP 概述

官方文档描述

Aspect-oriented Programming (AOP) complements Object-oriented Programming (OOP) by providing another way of thinking about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects enable the modularization of concerns (such as transaction management) that cut across multiple types and objects. (Such concerns are often termed “crosscutting” concerns in AOP literature.)

One of the key components of Spring is the AOP framework. While the Spring IoC container does not depend on AOP (meaning you do not need to use AOP if you don’t want to), AOP complements Spring IoC to provide a very capable middleware solution.

翻译

面向切面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP)。OOP 中模块化的关键单元是,而在 AOP 中模块化的关键是切面;切面使关注变(如事务管理)的模块可以跨越多种类型和对象(这种关注在 AOP 的文献中通常被称为“跨领域”关注)

Spring 的关键组件之一是 AOP 框架。尽管 Spring IOC 容器不依赖于 AOP (这意味着您不需要的话就不使用AOP),但 AOP 是对 Spring IoC 的补充,以提供功能强大的中间件解决方案。

上面是官方对 AOP 的解释,下面抽取一下上面的核心内容

  • AOP 是 OOP 的补充

    • OOP 开发代码,代码中设计到多个模块但逻辑相同对策代码会重复出现,虽然通过一些设计模式可以优化(策略,责任链,模板方法等),但是并不能完成有效的解决这些分散的相同逻辑造成的重复代码
    • AOP 可以将这些重复的代码逻辑抽取成一个切面,通过在运行时动态代理组合进原有的对象,照样可以实现预期效果
  • AOP 关注的核心是切面

    • 切面可以简单地理解为分散在不同类中的一组相同的逻辑
    • AOP 在对指定类的指定方法做逻辑增强时,就需要直接编写这些增强的逻辑,并切入到原有的代码中。
  • AOP 是对Spring IoC 的补充

    • 如果没有 AOP,IoC 本身也可以是 Spring 非常强大的特性
    • 只不过 AOP 可以在 IOC 容器中,针对需要的 Bean 去增强原有的功能(如给普通的 Service 实现事务控制)

AOP 的核心工作

上面的文档总结下来 AOP 的核心工作就是:解耦

AOP 将分散在各个类中方法的重复逻辑抽取为一个切面,并在运行时生成代理对象,将这些重复逻辑组合进原有的对象,这其实就是完成了原有业务与扩展逻辑之间的解耦。

通过这种解耦,最大的好处也就显而易见了:业务逻辑只需要关注业务逻辑,每个扩展逻辑也都只关心自己的逻辑,以及切入业务逻辑的位置即可。

AOP的基本术语

为方便解释 AOP 中的术语,在这里顺便再来搞一个场景:

image-1654565525230

image-1654565588066

在这个场景中,左边的主管视为 “原始对象” ,主管可提供账户充值、账号解封等业务,意为一个 Class 中定义的几个方法;中间的业务经理视为 “中间的代理层” ,他平时招揽客人,并且将客人的需求传达给里面的主管;右边开门办业务的视为 “客户端” ,办业务的时候都是由它发起。

Target:目标对象

这个应该是最好理解的了,目标对象就是被代理的对象。上面的场景中很明显左边的主管就是 Target

反映到前面的动态代理的例子中,这个 partner 就可以称作 Target :

public static Partner getPartner(int money) {
    // partner即为目标对象
    Partner partner = partners.remove(0);
    return (Partner) Proxy.newProxyInstance(......);
}

Proxy:代理对象

也是很好理解吧,代理对象就是上面代码中 Proxy.newProxyInstance 返回的结果。

在上面的场景中,中间的业务经理 + 左边的主管,组合形成一个代理对象(代理对象中还包含原始对象本身)。

JoinPoint:连接点

所谓连接点,可以简单的理解为目标对象的所属类中,定义的所有方法。由于 SpringFramework 支持的连接点只有方法,所以我们这样理解就没错。在上面的场景中,很明显主管提供的几项业务(账号充值、账户解封)就属于连接点

反映到前面动态代理的例子中,Partner 接口中的两个方法就叫连接点:

public interface Partner {
    void receiveMoney(int money);
    void playWith(Player player);
}

Pointcut:切入点

切入点,它的含义是那些被拦截 / 被增强的连接点。这个概念似乎不是很好理解了,咱继续看上面的场景。中间的业务经理在给主管传话的时候,并不是每次都实话实说,但也不都是瞎说,很明显他是看到有充值这样的涉及钱的业务,就开始胡说八道了,而没有涉及到钱的业务,他就如实转述。那我们是不是可以这样去理解:代理层会选择目标对象的一部分连接点作为切入点,在目标对象的方法执行前 / 后作出额外的动作。

所以,由这个解释,是不是就比较容易理解了?切入点与连接点的关系应该是包含关系:切入点可以是 0 个或多个(甚至全部)连接点的组合。

注意:切入点一定是连接点,连接点不一定是切入点。

Advice:通知

Advice 直接翻译过来就叫通知,但这个概念似乎很抽象,我们换一个词:增强的逻辑,也就是增强的代码

这下就好理解多了吧!上面的场景中,业务经理发现有人要充值的时候,它并没有直接传话给主管,而是先执行了他自己的逻辑:胡说八道,而在传话之前的这个胡说八道,就是业务主管针对账户充值这个连接点的增强逻辑

由此可以得出一个这样的结论:Proxy 代理对象 = Target 目标对象 + Advice 通知

所以是不是突然意识到一个问题,切入点和通知是要配合在一起使用的,有了切入点之后,需要搭配上增强的逻辑,才能算是给目标对象进行了代理、增强。

Aspect:切面

Aspect 切面 = PointCut 切入点 + Advice 通知

前面我们写的 InvocationHandler 的匿名内部类也好,MethodInterceptor 的匿名内部类也好,这些都可以看作是切面

Weaving:织入

从名字上听起来,它有点像一个动作,猜测:织入就是将 Advice 通知应用到 Target 目标对象,进而生成 Proxy 代理对象的过程。

Proxy 代理对象 = Target 目标对象 + Advice 通知,这个算式中的加号,就是织入。试想,目标对象和通知都有了,得需要一个动作将它们两个绑定到一起,就好比上面的场景中,主管找到这个在外招呼客人的经理后,也是要签了合同或者协议,经理才开始干活的呀。

所以,这个织入的动作,就比较容易理解了吧。

Introduction:引介

引介 / 引入,这个概念对标的是通知通知是针对切入点提供增强的逻辑,而引介是针对 Class 类它可以在不修改原有类代码的前提下,在运行期为原始类动态添加新的属性 / 方法

通知的类型

在 SpringFramework 的官方文档 AOP 术语的介绍之后,紧跟着就说了 Spring 中定义的通知的类型。SpringFramework 中支持的通知的类型包含 5 种,这些通知的类型是基于 AspectJ 的:

  • Before 前置通知:目标对象的方法调用之前触发
  • After 后置通知:目标对象的方法调用后触发
  • AfterReturning 返回通知:目标对象的方法调用完成,在返回结果值之后触发
  • AfterThrowing 异常通知:目标对象的方法在执行过程中抛出异常 / 触发异常后触发
    • 注意一点,AfterReturning 与 AfterThrowing 两者是互斥的!如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
  • Around 环绕通知:编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种。因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事情,甚至不调用目标对象的方法。

这几种通知的体现,目前我们只编写过环绕通知,而这些环绕通知,其实就是 InvocationHandler 或 MethodInterceptor 的匿名内部类:

image-1654566873694

在之前写的代码中,method.invoke 方法很明显是对目标方法的调用,而这之前和之后的代码,加到一起,就是环绕通知的内容。

小结与思考

  1. 什么是 Spring 中的 AOP?
    AOP (Aspect-oriented Programming)面向切面编程,它是对 OOP 的补充,AOP可以将那些重复的代码逻辑抽取出来,形成一个切面,然后通过代理技术织入到目标对象中去,对目标对象的特定方法功能进行增强 / 或做一些特殊处理。切面就是分散在多个不同类中的一组相同逻辑的代码。AOP 是对 Spring IoC 的补充。
  2. Spring 的 AOP 中包含哪些核心概念?它们分表都代表了什么?
  • Target:目标对象,被代理的对象
  • Proxy:代理对象,对目标对象的代理,包含了原始对象本身,和增强的逻辑
  • JoinPoint:连接点,目标对象中的方法
  • Pointcut:切入点,目标对象被增强的方法
  • Advice:通知,增强的逻辑代码
  • Aspect:切面,Aspect 切面 = PointCut 切入点 + Advice 通知,InvocationHandler 的匿名内部类,MethodInterceptor 的匿名内部类,这些都可以看作是切面。
  • Weaving:织入,将 Advice 通知应用到 Target 目标对象,进而生成 Proxy 代理对象的过程
  • Introduction:引介,引介是针对 Class 类,它可以在不修改原有类代码的前提下,在运行期为原始类动态添加新的属性 / 方法。
  1. Spring 中的通知包含哪几种?分别的执行时机都是什么
  • Before:前置通知,目标对象方法执行前触发
  • After:后置通知,目标对象方法执行后触发
  • AfterReturning:返回通知,目标对象方法执行完,返回数据后触发
  • AfterThrowing:异常通知,目标对象方法执行出现异常只会触发
  • Around:环绕通知,是上面几种通知的集合。

AspectJ 切面表达式 详解

pointcut 中需要定义一个切面表达式,以此来约束 AOP 的范围

切面表达式参数:execution(public * com.rsthe.service….(…) throws java.lang.Exception)
execution 中包含的就是切面表达式,execution 表示声明一个切面表达式
execution 中参数解释:
第一个参数:访问修饰符(public,private等,* 表示任意)
第二个参数:返回值类型(void, Integer, Object等, * 表示任意)
第三个参数:AOP 作用的包路径(.. 表示多重路径,* 表示 任意包名、..* 表示任意路径下任意包名)
第四个参数:类名(* 任意类名)
第五个参数:方法名(* 任意方法)
第六个参数:方法参数类型列表(* 表示一个任意参数类型,.. 表示多个任意参数类型)
第七个参数:可选,异常类型,对于某些显式声明了会抛出异常的方法,可以使用异常通知来切入这部分方法。


# AOP