【重写SpringFramework】第二章aop模块:AOP概述(chapter 2-1)

1. 前言

在现代社会中,人们在衣食住行各个方面都享受着前所未有的便利。出行可以乘坐高铁或飞机,无所不在的网络让信息流动起来,网购和快递则依赖高效的物流配送。事实证明,公共服务越完善,工作的效率越高,创造的社会效益也就越大。公共服务提供的是特定的功能,这种共性被称为横切关注点。如何理解这句话?每个人都有自己的事情要做,他们选择某一项公共服务是由需求决定的。需求驱动的服务就是关注点,横切则涵盖了一个范围,即公共服务是为特定的人群提供服务的。

面向对象编程(OOP)关注的是业务逻辑,不同业务的实现也不一样。面向切面编程(AOP)关注的是公共服务,比如日志、缓存、事务、权限、统计等,它们关注广泛存在于业务逻辑中,构成了一个个横切关注点。一般而言,AOP 可以看作是一种编程范式,作用是将横切关注点从业务逻辑中分离出来,降低了系统的耦合度,从而提高代码的可维护性和重用性。

2. AOP 规范

在深入分析 Spring AOP 的实现之前,我们有必要了解一下 AOP 的规范。令人有些意外的是,面向切面编程的规范并不是由 Java 定义的,一个名叫 AOP 联盟(aop alliance)的组织完成了顶层逻辑的设计。我们在工程中引入了 aopalliance 包,虽然只有寥寥数个接口,却对 AOP 做了高度的抽象。

在这里插入图片描述

从类图可以看到,AOP 大体可以分为两个部分。其中 Advice 表示增强的语义,Joinpoint 表示系统的执行点。增强逻辑一定是作用于某段代码上的,Joinpoint 充当了桥梁的作用,使得 Advice 可以嵌入到业务代码中。

3. 连接点

3.1 概述

什么是连接点,这个问题很重要,我们先来看官方给出的定义:

This interface represents a generic runtime joinpoint. A runtime joinpoint is an event that occurs on a static joinpoint.

Joinpoint 接口表示通用的运行时连接点,运行时连接点是指在静态连接点上发生的事件。

静态连接点(static joinpoint)是指程序中的某个位置,这个位置使用 AccessbileObject 来描述。AccessbileObject 表示可访问的对象,包括字段、方法和构造器。当我们定义了一个方法,可以通过反射获取方法的名称、参数、返回值、以及声明的注解等信息。这些信息在编译的时候就已经确定了,所以才说是静态的

在这里插入图片描述

3.2 Joinpoint

Joinpoint 接口定义了运行时连接点,一个运行时连接点包括两部分。一是静态连接点,也就是可访问的字段、方法和构造器。二是连接点上发生的事件,也就是说这些对象将被访问。

  • proceed 方法:访问静态连接点,比如调用方法,并在调用方法的过程中执行增强逻辑。
  • getThis 方法:返回连接点的静态部分所属的对象。字段、方法不能独立存在,必须定义在某个类中。
  • getStaticPart 方法:返回连接点的静态部分,即字段、方法和构造器。
public interface Joinpoint {
    Object proceed() throws Throwable;
    Object getThis();
    AccessibleObject getStaticPart();
}

3.3 Invocation

不同的静态连接点上发生的事件,有不同的叫法。对于方法和构造器来说,叫做调用(invocation),对于字段来说,叫做访问(access)。Invocation 接口就是用来处理方法和构造器的,getArguments 方法的作用是获取参数值,而调用方法和构造器是可以传递参数的。

public interface Invocation extends Joinpoint {
    Object[] getArguments();
}

Incovation 接口有两个子接口,分别对应方法和构造器。getMethodgetConstructor 方法实际上是 getStaticPart 方法的具体化。对于方法调用来说,静态部分就是 Method,对于构造器来说,静态部分则是 Constructor。我们可以把构造器看成是特殊的方法,只要是方法就会有参数,因此 getArgument 方法被定义在父接口中。

public interface MethodInvocation extends Invocation {
    Method getMethod();
}

public interface ConstructorInvocation extends Invocation {
    Constructor getConstructor();
}

3.4 事件三要素

Joinpoint 接口的定义中提到,运行时连接点是指在静态连接点上发生的事件,这句话该如何理解?在程序运行时,方法和构造器被调用,或者字段被取值或赋值,这些行为的发生被认为是一次事件。单就解释来说仍有点抽象,我们以 HTML 页面为例说明。(当然也可以换成桌面窗口程序或手机 App)

一个页面上可能有多个控件,比如按钮、输入框等。当鼠标单击按钮时,会生成一个 click 事件;当文本输入框中的内容改变时,会生成 key-down 等事件。既然是事件,就要满足三个要素。一是事件源,也就是产生事件的载体。二是事件携带的数据,比如文本框的内容。三是处理事件的逻辑,比如在 JavaScript 代码中添加一个监听器。我们可以从 JointpointInvocation 接口定义的方法中找到对应的三要素,如下所示:

  • getStaticPart 方法:返回静态连接点,也就是事件源
  • getArguments 方法:当方法和构造器被调用时,传入的参数就是事件携带的数据
  • proceed 方法:调用方法或构造器时,执行增强逻辑,实际上完成了对事件的处理

4. 通知

4.1 概述

当我们把类看作一个页面,其中的方法相当于一个按钮。点击按钮时会生成一个事件,然后由监听器来处理。同理,当调用某个方法时,也会产生一个事件。区别在于,监听器会将事件消费掉,而拦截器在调用目标方法的同时,还执行了增强的逻辑。Joinpoint 接口只规定了事件的三要素,现在还需要一个增强逻辑,Advice 接口充当了这一角色。

Advice 是一个标记接口,具体实现可以是任意类型的增强,比如拦截器。Interceptor 也是一个标记接口,其定义如下:

A generic interceptor can intercept runtime events that occur within a base program. Those events are materialized by (reified in) joinpoints.

Interceptor 的作用是拦截源程序上发生的运行时事件,这些事件由若干连接点所产生。

源程序(base program)是指业务代码,调用目标方法就是所谓的运行时事件。这些事件由拦截器来处理,实际上提供了增强的语义。若干连接点(joinpoints)的潜含义是拦截器可以应用于多个方法或构造器,如果把一个方法或构造器视为一个点,那么多个方法或构造器就构成了一个切面。

4.2 方法与构造器拦截

根据连接点的不同,Interceptor 也有相应的子接口。MethodInterceptor 接口的作用是对方法调用进行拦截,ConstructorInterceptor 接口的作用是对构造器调用进行拦截。

public interface MethodInterceptor extends Interceptor {
    Object invoke(MethodInvocation invocation) throws Throwable;
}

public interface ConstructorInterceptor extends Interceptor {
    Object construct(ConstructorInvocation invocation) throws Throwable;
}

4.3 字段访问

AOP 规范虽然没有定义访问字段的组件,但明里暗里透露了不少信息。事实上,在 Interceptor 接口的注释中,我们找到了一些蛛丝马迹。

在这里插入图片描述

根据以上的示例代码,我们可以依照调用方法和构造器的思路,予以实现。首先是 FieldAccess 接口,定义了访问字段的行为。

  • getField 方法表示具体的静态部分,同样是 getThis 方法的具体化。
  • getValueToSet 方法表示赋给字段的值,相当于 Invocation 接口的 getArguments 方法,同样表示事件携带的数据。
public interface FieldAccess extends Joinpoint {
    Field getField();
    Object getValueToSet();
}

其次是 FieldInterceptor 接口,访问字段有两种操作(方法和构造器只有调用这一种操作),get 方法的作用是获取字段的值,set 方法的作用是为字段赋值。

public interface FieldInterceptor extends Interceptor {
    Object get(FieldAccess fa) throws Throwable;
    Object set(FieldAccess fa) throws Throwable;
}

5. AOP 实现

AOP 规范是高度抽象的,没有明确指出 AOP 实现的路线图。但我们可以从接口和方法的定义中获取信息,总的来说,AOP 的实现需要满足三要素,我们将在接下来的内容详细讨论。简单介绍如下:

  • 增强:在业务代码之外执行额外的增强逻辑,这是对被分离的关注点的抽象。
  • 切面:增强逻辑可以应用在若干连接点上,这些连接点构成了一个切面。换句话说,切面是由需求驱动的,只要有需要就能享受到某项公共服务。
  • 代理:方法、构造器和字段是静态连接点,它们必定属于某一个对象。将增强逻辑应用于对象的过程称为织入,一般通过代理机制来实现。

6. 总结

本节介绍了面向切面编程的基本概念,aopalliance 包对面向切面编程进行了高度的抽象。AOP 框架的顶级接口有两个,分别是 JoinpointAdvice。其中,Advice 接口表示增强逻辑,Joinpoint 接口表示程序执行中的一个点,比如调用方法或访问字段。增强逻辑通过连接点介入到业务代码之中,这是 AOP 最基本的内涵。

连接点是一个复合概念,包括连接点的静态部分和在静态部分上发生的事件。所谓连接点的静态部分是指可访问的对象,分别是方法、构造器和字段。因此,连接点上发生的事件也分为三种,即方法调用、构造器调用和字段访问。需要注意的是,连接点与通知是一一对应的,比如方法调用对应方法拦截器,字段访问对应字段拦截器。

AOP 规范将连接点的调用或访问抽象成事件,既然如此,就需要满足事件的三要素。首先是事件源,也就是静态连接点,即方法、构造器和字段。其次是数据,当方法和构造器被调用时,需要传递参数。最后是事件处理逻辑,在调用方法或构造器的同时,还执行了额外的增强逻辑。

面向切面编程定义了很多概念,但 AOP 实现最终落实在三个方面,即增强、切面和代理。我们将在接下来的内容中着重介绍这三个核心要素,所谓纲举目张,就是抓住主要矛盾来分析和解决问题。

在这里插入图片描述


欢迎关注公众号【Java编程探微】,加群一起讨论。
在这里插入图片描述

  • 39
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值