其他分享
首页 > 其他分享> > 如何实现一个简易版的 Spring - 如何实现 AOP(上)

如何实现一个简易版的 Spring - 如何实现 AOP(上)

作者:互联网

前言

本文是「如何实现一个简易版的 Spring 系列」的第五篇,在之前介绍了 Spring 中的核心技术之一 IoC,从这篇开始我们再来看看 Spring 的另一个重要的技术——AOP。用过 Spring 框架进行开发的朋友们相信或多或少应该接触过 AOP,用中文描述就是面向切面编程。学习一个新技术了解其产生的背景是至关重要的,在刚开始接触 AOP 时不知道你有没有想过这个问题,既然在面向对象的语言中已经有了 OOP 了,为什么还需要 AOP 呢?换个问法也就是说在 OOP 中有哪些场景其实处理得并不优雅,需要重新寻找一种新的技术去解决处理?(P.S. 这里建议暂停十秒钟,自己先想一想...)

为什么需要 AOP

我们做软件开发的最终目的是为了解决公司的各种需求,为业务赋能,注意,这里的需求包含了业务需求和系统需求,对于绝大部分的业务需求的普通关注点,都可以通过面向对象(OOP)的方式对其进行很好的抽象、封装以及模块化,但是对于系统需求使用面向对象的方式虽然很好的对其进行分解并对其模块化,但是却不能很好的避免这些类似的系统需求在系统的各个模块中到处散落的问题。

why-need-aop.png

因此,需要去重新寻找一种更好的办法,可以在基于 OOP 的基础上提供一套全新的方法来处理上面的问题,或者说是对 OOP 面向对象的开发模式做一个补充,使其可以更优雅的处理上面的问题,迄今为止 Spring 提供一个的解决方案就是面向切面编程——AOP。有了 AOP 后,我们可以将这些事务管理、系统日志以及安全检查等系统需求(横切关注点:cross-cutting concern)进行模块化的组织,使得整个系统更加的模块化方便后续的管理和维护。细心的你应该发现在 AOP 里面引入了一个关键的抽象就是切面(Aspect),用于对于系统中的一些横切关注点进行封装,要明确的一点是 AOP 和 OOP 不是非此即彼的对立关系,AOP 是对 OOP 的一种补充和完善,可以相互协作来完成需求,Aspect 对于 AOP 的重要程度就像 Class 对 OOP 一样。

use-aop-arc.png

几个重要的概念

我们最终的目的是要模仿 Spring 框架自己去实现一个简易版的 AOP 出来,虽然是简易版但是会涉及到 Spring AOP 中的核心思想和主要实现步骤,不过在此之前先来看看 AOP 中的重要概念,同时也是为以后的实现打下理论基础,这里需要说明一点是我不会使用中文翻译去描述这些 AOP 定义的术语(另外,业界 AOP 术语本来就不太统一),你需要重点理解的是术语在 AOP 中代表的含义,就像我们不会把 Spring 给翻译成春天一样,在软件开发交流你知道它表示一个 Java 开发框架就可以了。下面对其关键术语进行逐个介绍:

Joinpoint

A point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution. -- Spring Docs

通过之前的介绍可知,在我们的系统运行之前,需要将 AOP 定义的一些横切关注点(功能模块)织入(可以简单理解为嵌入)到系统的一些业务模块当中去,想要完成织入的前提是我们需要知道可以在哪些执行点上进行操作,这些执行点就是 Joinpoint。下面看个简单示例:

/**
 * @author mghio
 * @since 2021-05-22
 */
public class Developer {

  private String name;

  private Integer age;

  private String siteUrl;

  private String position;

  public Developer(String name, String siteUrl) {
    this.name = name;
    this.siteUrl = siteUrl;
  }

  public void setSiteUrl(String siteUrl) {
    this.siteUrl = siteUrl;
  }

  public void setAge(Integer age) {
    this.age = age;
  }

  public void setName(String name) {
    this.name = name;
  }

  public void setPosition(String position) {
    this.position = position;
  }

  public void showMainIntro() {
    System.out.printf("name:[%s], siteUrl:[%s]\n", this.name, this.siteUrl);
  }

  public void showAllIntro() {
    System.out.printf("name:[%s], age:[%s], siteUrl:[%s], position:[%s]\n",
        this.name, this.age, this.siteUrl, this.position);
  }

}
/**
 * @author mghio
 * @since 2021-05-22
 */
public class DeveloperTest {

  @Test
  public void test() {
    Developer developer = new Developer("mghio", "https://www.mghio.cn");
    developer.showMainIntro();
    developer.setAge(18);
    developer.setPosition("中国·上海");
    developer.showAllIntro();
  }

}

理论上,在上面示例的这个 test() 方法调用中,我们可以选择在 Developer 的构造方法执行时进行织入,也可以在 showMainIntro() 方法的执行点上进行织入(被调用的地方或者在方法内部执行的地方),或者在 setAge() 方法设置 sge 字段时织入,实际上,只要你想可以在 test() 方法的任何一个执行点上执行织入,这些可以织入的执行点就是 Joinpoint。
这么说可能比较抽象,下面通过 test() 方法调用的时序图来直观的看看:

aop-weaving.png

从方法执行的时序来看不难发现,会有如下的一些常见的 Joinpoint 类型:

虽然理论上,在程序执行中的任何执行点都可以作为 Joinpoint,但是在某些类型的执行点上进行织入操作,付出的代价比较大,所以在 Spring 中的 Joinpoint 只支持方法执行(Method execution)这一种类型(这一点从 Spring 的官方文档上也有说明),实际上这种类型就可以满足绝大部分的场景了。

Pointcut

A predicate that matches join points. Advice is associated with a pointcut expression and runs at any join point matched by the pointcut (for example, the execution of a method with a certain name). The concept of join points as matched by pointcut expressions is central to AOP, and Spring uses the AspectJ pointcut expression language by default.-- by Spring Docs

Pointcut 表示的是一类 Jointpoint 的表述方式,在进行织入时需要根据 Pointcut 的配置,然后往那些匹配的 Joinpoint 织入横切的逻辑。这里面临的第一个问题:用人类的自然语言可以很快速的表述哪些我们需要织入的 Joinpoint,但是在代码里要如何去表述这些 Joinpoint 呢?
目前有如下的一些表述 Joinpoint 定义的方式:

另外 Pointcut 也支持进行一些简单的逻辑运算,这时我们就可以将多个简单的 Pointcut 通过逻辑运算组合为一个比较复杂的 Pointcut 了,比如在 Spring 配置中的 and 和 or 等逻辑运算标识符以及 AspectJ 中的 && 和 || 等逻辑运算符。

Advice

Action taken by an aspect at a particular join point. Different types of advice include “around”, “before” and “after” advice. (Advice types are discussed later.) Many AOP frameworks, including Spring, model an advice as an interceptor and maintain a chain of interceptors around the join point.-- by Spring Docs

Advice 表示的是一个注入到 Joinpoint 的横切逻辑,是一个横切关注点逻辑的抽象载体。按照 Advice 的执行点的位置和功能的不同,分为如下几种主要的类型:

Aspect

A modularization of a concern that cuts across multiple classes. Transaction management is a good example of a crosscutting concern in enterprise Java applications. In Spring AOP, aspects are implemented by using regular classes (the schema-based approach) or regular classes annotated with the @Aspect annotation (the @AspectJ style). -- Spring Docs

Aspect 是对我们系统里的横切关注点(crosscutting concern)包装后的一个抽象概念,可以包含多个 Joinpoint 以及多个 Advice 的定义。Spring 集成了 AspectJ 后,也可以使用 @AspectJ 风格的声明式指定一个 Aspect,只要添加 @Aspect 注解即可。

Target object

An object being advised by one or more aspects. Also referred to as the “advised object”. Since Spring AOP is implemented by using runtime proxies, this object is always a proxied object. -- by Spring Docs

目标对象一般是指那些可以匹配上 Pointcut 声明条件,被织入横切逻辑的对象,正常情况下是由 Pointcut 来确定的,会根据 Pointcut 设置条件的不同而不同。
有了 AOP 这些概念后就可以把上文的例子再次进行整理,各个概念所在的位置如下图所示:

aop-concept.png

总结

本文首先对 AOP 技术的诞生背景做了简要介绍,后面介绍了 AOP 的几个重要概念为后面我们自己实现简易版 AOP 打下基础,AOP 是对 OOP 的一种补充和完善,文中列出的几个概念只是 AOP 中涉及的概念中的冰山一角,想要深入了解更多的相关概念的朋友们可以看 官方文档 学习,下篇是介绍 AOP 实现依赖的一些基础技术,敬请期待。转发、分享都是对我的支持,我将更有动力坚持原创分享!

标签:Spring,Advice,Joinpoint,简易版,AOP,执行,方法
来源: https://www.cnblogs.com/mghio/p/14800628.html