likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 22

1. 前言

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

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

2. AOP 规范

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

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

从类图可以看到,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 表示可访问的对象,包括字段、方法和构造器。当我们定义了一个方法,可以通过反射获取方法的名称、参数、返回值、以及声明的注解等信息。这些信息在编译的时候就已经确定了,所以才说是静态的

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

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 接口的注释中,我们找到了一些蛛丝马迹。

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

根据以上的示例代码,我们可以依照调用方法和构造器的思路,予以实现。首先是 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 实现最终落实在三个方面,即增强、切面和代理。我们将在接下来的内容中着重介绍这三个核心要素,所谓纲举目张,就是抓住主要矛盾来分析和解决问题。

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


欢迎关注公众号【Java编程探微】,加群一起讨论。

原创不易,觉得内容不错请关注、点赞、收藏。

转载自:https://juejin.cn/post/7383894869397372966
评论
请登录