作者:安全小护士 | 来源:互联网 | 2024-10-31 19:58
本文作为“实现简易版Spring系列”的第五篇,继前文深入探讨了Spring框架的核心技术之一——控制反转(IoC)之后,将重点转向另一个关键技术——面向切面编程(AOP)。对于使用Spring框架进行开发的开发者来说,AOP是一个不可或缺的概念。了解AOP的背景及其基本原理,对于掌握这一技术至关重要。本文将通过具体示例,详细解析AOP的实现机制,帮助读者更好地理解和应用这一技术。
前言
本文是「如何实现一个简易版的 Spring 系列」的第五篇,在之前介绍了 Spring 中的核心技术之一 IoC,从这篇开始咱们再来看看 Spring 的另一个重要的技术——AOP。用过 Spring 框架进行开发的敌人们置信或多或少应该接触过 AOP,用中文形容就是面向切面编程。学习一个新技术理解其产生的背景是至关重要的,在刚开始接触 AOP 时不晓得你有没有想过这个问题,既然在面向对象的语言中曾经有了 OOP 了,为什么还须要 AOP 呢?换个问法也就是说在 OOP 中有哪些场景其实解决得并不优雅,须要从新寻找一种新的技术去解决解决?(P.S. 这里倡议暂停十秒钟,本人先想一想…)
为什么须要 AOP
咱们做软件开发的最终目标是为了解决公司的各种需要,为业务赋能,留神,这里的需要蕴含了业务需要和零碎需要,对于绝大部分的业务需要的一般关注点,都能够通过面向对象(OOP)的形式对其进行很好的形象、封装以及模块化,然而对于零碎需要应用面向对象的形式尽管很好的对其进行合成并对其模块化,然而却不能很好的防止这些相似的零碎需要在零碎的各个模块中到处散落的问题。
因而,须要去从新寻找一种更好的方法,能够在基于 OOP 的根底上提供一套全新的办法来解决下面的问题,或者说是对 OOP 面向对象的开发模式做一个补充,使其能够更优雅的解决下面的问题,迄今为止 Spring 提供一个的解决方案就是面向切面编程——AOP。有了 AOP 后,咱们能够将这些事务管理、系统日志以及安全检查等零碎需要(横切关注点:cross-cutting concern)进行模块化的组织,使得整个零碎更加的模块化不便后续的治理和保护。仔细的你应该发现在 AOP 外面引入了一个要害的形象就是切面(Aspect),用于对于零碎中的一些横切关注点进行封装,要明确的一点是 AOP 和 OOP 不是非此即彼的对抗关系,AOP 是对 OOP 的一种补充和欠缺,能够相互协作来实现需要,Aspect 对于 AOP 的重要水平就像 Class 对 OOP 一样。
几个重要的概念
咱们最终的目标是要模拟 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() 办法调用的时序图来直观的看看:
从办法执行的时序来看不难发现,会有如下的一些常见的 Joinpoint 类型:
- 构造方法调用(Constructor Call)。对某个对象调用其构造方法进行初始化的执行点,比方以上代码中的 Developer developer = new Developer(“mghio”, “https://www.mghio.cn”);。
- 办法调用(Method call)。调用某个对象的办法时所在的执行点,实际上构造方法调用也是办法调用的一种非凡状况,只是这里的办法是构造方法而已,比方示例中的 developer.showMainIntro(); 和 developer.showAllIntro(); 都是这种类型。
- 办法执行(Method execution)。当某个办法被调用时办法外部所处的程序的执行点,这是被调用办法外部的执行点,与办法调用不同,办法执行入以上办法时序图中标注所示。
- 字段设置(Field set)。调用对象 setter 办法设置对象字段的代码执行点,触发点是对象的属性被设置,和设置的形式无关。以上示例中的 developer.setAge(18); 和 developer.setPosition(“中国.上海”); 都是这种类型。
- 类初始化(Class initialization)。类中的一些动态字段或者动态代码块的初始化执行点,在以上示例中没有体现。
- 异样执行(Exception execution)。类的某些办法抛出异样后对应的异样解决逻辑的执行点,在以上示例中没有这种类型。
尽管实践上,在程序执行中的任何执行点都能够作为 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 定义的形式:
- 间接指定织入的办法名。不言而喻,这种表述形式尽管简略,然而所反对的性能比拟繁多,只实用于办法类型的 Joinpoint,而且当咱们零碎中须要织入的办法比拟多时,一个一个的去定义织入的 Pointjoint 时过于麻烦。
- 正则表达式形式。正则表达式置信大家都有一些理解,性能很弱小,能够匹配示意多个不同办法类型的 Jointpoint,Spring 框架的 AOP 也反对这种表述形式。
- Pointcut 特定语言形式。这个因为是一种特定畛域语言(DSL),所以其提供的性能也是最为灵便和丰盛的,这也导致了不论其应用和实现复杂度都比拟高,像 AspectJ 就是应用的这种表述形式,当然 Spring 也反对。
另外 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 的执行点的地位和性能的不同,分为如下几种次要的类型:
- Before Advice。Before Advice 示意是在匹配的 Joinpoint 地位之前执行的类型。如果被胜利织入到办法类型的 Joinpoint 中,那么 Beofre Advice 就会在这个办法执行之前执行,还有一点须要留神的是,如果须要在 Before Advice 中完结办法的执行,咱们能够通过在 Advice 中抛出异样的形式来完结办法的执行。
- After Advice。不言而喻,After Advice 示意在配置的 Joinpoint 地位之后执行的类型。能够在细分为 After returning Advice、After throwing Advice 和 After finally Advice 三种类型。其中 After returning Advice 示意的是匹配的 Joinpoint 办法失常执行实现(没有抛出异样)后执行;After throwing Advice 示意匹配的 Joinpoint 办法执行过程中抛出异样没有失常返回后执行;After finally Advice 示意办法类型的 Joinpoint 的不论是失常执行还是抛出异样都会执行。
这几种 Advice 类型在办法类型的 Joinpoint 中执行程序如下图所示:
- Around Advice。这种类型是性能最为弱小的 Advice,能够匹配的 Joinpoint 之前、之后甚至终端原来 Joinpoint 的执行流程,失常状况下,会先执行 Joinpoint 之前的执行逻辑,而后是 Joinpoint 本人的执行流程,最初是执行 Joinpoint 之后的执行逻辑。仔细的你应该发现了,这不就是下面介绍的 Before Advice 和 After Advice 类型的组合吗,是的,它能够实现这两个类型的性能,不过还是要依据具体的场景抉择适合的 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 技术的诞生背景做了简要介绍,前面介绍了 AOP 的几个重要概念为前面咱们本人实现简易版 AOP 打下基础,AOP 是对 OOP 的一种补充和欠缺,文中列出的几个概念只是 AOP 中波及的概念中的冰山一角,想要深刻理解更多的相干概念的敌人们能够看 官网文档 学习,下篇是介绍 AOP 实现依赖的一些根底技术,敬请期待。转发、分享都是对我的反对,我将更有能源保持原创分享!