Spring源码系列(四)--spring-aop是如何设计的
简介
spring-aop 用于生成动态代理类(底层是使用 JDK 动态代理或 cglib 来生成代理类),搭配 spring-bean 一起使用,可以使 AOP 更加解耦、方便。在实际项目中,spring-aop 被广泛用来实现日志、权限、事务、异常等的统一管理。
上一篇博客(Spring源码系列(三)--spring-aop的基础组件、架构和使用)简单讲了 spring-aop 的基础组件、架构和使用方法,本文将开始研究 spring-aop 的源码,主要分成以下部分:
- spring-aop 的几个重要的组件,如 Joinpoint、Advice、Pointcut、Advisor 等;
- spring-aop 是如何设计的
项目环境
maven:3.6.3
操作系统:win10
JDK:8u231
Spring:5.2.6.RELEASE
一点补充
在第一篇博客中,我们使用 spring-aop 提供的代理工厂来生成动态代理类,被代理的对象可以是我们自己 new 的一个对象,也可以是 bean。因为 spring-aop 最主要的功能就是生成动态代理类,所以,本文的源码分析都只围绕这个功能展开,并不会掺杂 spring-bean 的内容。
实际项目中,Spring 可以“悄无声息”地完成对 bean 的代理,本质是通过注册BeanPostProcessor
来实现,原理并不复杂。如果你对 spring-bean 感兴趣的话,可以参考博客Spring源码系列(二)--bean组件的源码分析。
最后,和以往不同,
几个重要的组件
说到 spring-aop,我们经常会提到Pointcut
、Joinpoint
、Advice
、Aspect
等等概念,它们都是抽象出来的“标准”,有的来自 aopalliance,有的来自 AspectJ,也有的是 spring-aop 原创。
它们是构成 spring-aop “设计图”的基础,理解它们非常难,一个原因是网上能讲清楚的不多,第二个原因是这些组件本身抽象得不够直观(spring 官网承认了这一点)。
对Joinpoint做Advice
在 spring-aop 的包中内嵌了 aopalliance 的包(aopalliance 就是一个制定 AOP 标准的联盟、组织),这个包是 AOP 联盟提供的一套“标准”,提供了 AOP 一些通用的组件,包的结构大致如下。
└─org
└─aopalliance
├─aop
│ Advice.class
│ AspectException.class
│
└─intercept
ConstructorInterceptor.class
ConstructorInvocation.class
Interceptor.class
Invocation.class
Joinpoint.class
MethodInterceptor.class
MethodInvocation.class
使用 UML 表示以上类的关系,如下。可以看到,这主要包含两个部分:Joinpoint
和Advice
(这是 AOP 最核心的两个概念)。完整的 aopalliance 包,除了 aop 和 intercept,还包括了 instrument 和 reflect,后面这两个部分 spring-aop 没有引入,这里就不说了。
- Joinpoint
Joinpoint
表示调用某个方法(构造方法或成员方法),或者操作某个成员属性的事件。
例如,我调用了user.save()
方法,这个事件就属于一个Joinpoint
。Joinpoint
是一个“动态”的概念,Field
、Method
、或Constructor
等对象是它的静态部分。
如上图所示,Joinpoint
是Advice
操作的对象。
在 spring-aop 中,主要使用Joinpoint
的子接口--MethodInvocation
,JDK 动态代理使用的MethodInvocation
实现类为ReflectiveMethodInvocation
,cglib 使用的是MethodInvocation
实现类为CglibMethodInvocation
。
- Advice
对Joinpoint
执行的某些操作。
例如,JDK 动态代理使用的InvocationHandler
、cglib 使用的MethodInterceptor
,在抽象概念上可以算是Advice
(即使它们没有继承Advice
)。
在 spring-aop 中,主要使用Advice
的子接口--MethodInterceptor
。
为了更好地理解这两个概念,我再举一个例子:当我们对用户进行增删改查前,进行权限校验。其中,调用用户的新增方法的事件就是一个的Joinpoint
,权限校验就是一个Advice
,即对Joinpoint
做Advice
。
在 spring-aop 中,Joinpoint
对象持有了一条Advice chain
,调用Joinpoint
的proceed()
方法将采用责任链的形式依次执行(注意,Advice
的执行可以互相嵌套,不是单纯的先后顺序)。
其他的几个概念
在 spring-aop 中,还会使用到其他的概念,例如Advice Filter
、Advisor
、Pointcut
、Aspect
等。
Advice Filter
Advice Filter
一般和Advice
绑定,它用来告诉我们,Advice
是否作用于指定的Joinpoint
,如果 true,则将Advice
加入到当前Joinpoint
的Advice chain
,如果为 false,则不加入。
在 spring-aop 中,常用的Advice Filter
包括ClassFilter
和MethodMatcher
,前者过滤的是类,后者过滤的是方法。
Pointcut
Pointcut
是 AspectJ 的组件,它一种 Advice Filter
。
在 spring-aop 中,Pointcut
=ClassFilter
+MethodMatcher
。
Advisor
Advisor
是 spring-aop 原创的组件,一个 Advisor = 一个 Advice Filter + 一个 Advice。
在 spring-aop 中,主要有两种Advisor
:IntroductionAdvisor
和PointcutAdvisor
。前者为ClassFilter
+Advice
,后者为Pointcut
+Advice
。
Aspect
Aspect
也是 AspectJ 的组件,一组同类的PointcutAdvisor
的集合就是一个Aspect
。
在下面代码中,printRequest 和 printResponse 都是Advice
,genericPointCut 是Pointcut
,printRequest + genericPointCut 是PointcutAdvisor
,UserServiceAspect 是Aspect
。
@Aspect
public class UserServiceAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(UserServiceAspect.class);
@Pointcut("execution(* cn.zzs.spring.UserService+.*(..)))")
public void genericPointCut() {
}
@Before(value = "genericPointCut()")
public void printRequest(JoinPoint joinPoint) throws InterruptedException {
//······
}
@After(value = "genericPointCut()")
public void printResponse(JoinPoint joinPoint) throws InterruptedException {
//······;
}
}
spring-aop是如何设计的
了解了 spring-aop 的重要组件,接下来就可以构建它的设计视图。spring-aop 的设计视图主要包括两个部分:生成代理类和代理方法的执行。
生成代理类
这里我画了一张 UML 图来简单说明。
AdvisedSupport
用来告诉AopProxy
如何生成代理对象,它描述了两部分信息:
- 对谁生成代理对象?--
TargetSource
。TargetSource
既可以返回单例对象,也可以返回多例对象,有点类似于我们常用的DataSource
。 - 生成的代理对象持有的 Advisor List。前面提到过,当我们执行代理方法时,将会采用责任链的方式执行
Advice chain
,而Advice chain
就是通过 Advisor List 过滤得到;
AopProxy
用来生成代理对象,spring-aop 提供了 JDK 动态代理和 cglib 动态代理两种AopProxy
实现。
除此之外,spring-aop 提供了三种代理工厂供调用者使用,其中ProxyFactory
比较普通,AspectJProxyFactory
支持 AspectJ 语法的代理工厂,ProxyFactoryBean
可以给 Spring IoC 管理的 bean 进行代理。上一篇博客已介绍过如何使用这三个代理工厂。
代理方法的执行
这里使用 cglib 的代理类来简单说明代理方法的执行过程。关于 cglib 的内容可以参考: 源码详解系列(一)------cglib动态代理的使用和分析
当我们调用代理的方法时,代理方法中将生成一个Joinpoint
对象--即图中的CglibMethodInvocation
,它持有了一条Advice chain
,而Advice chain
通过 Advisor List 过滤得到,调用Joinpoint
的proceed()
方法就可以执行Advice chain
。
以上简单介绍了 spring-aop 的设计视图,有了这些,相信读者会更容易读懂具体的源码。
感谢阅读。以上内容如有错误,欢迎指正。
相关源码请移步:spring-aop
本文为原创文章,转载请附上原文出处链接:https://www.cnblogs.com/ZhangZiSheng001/p/13745168.html
Spring源码系列(四)--spring-aop是如何设计的的更多相关文章
- AOP执行增强-Spring 源码系列(5)
AOP增强实现-Spring 源码系列(5) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostProc ...
- Spring源码系列 — BeanDefinition扩展点
前言 前文介绍了Spring Bean的生命周期,也算是XML IOC系列的完结.但是Spring的博大精深,还有很多盲点需要摸索.整合前面的系列文章,从Resource到BeanDefinition ...
- Spring源码系列 — Bean生命周期
前言 上篇文章中介绍了Spring容器的扩展点,这个是在Bean的创建过程之前执行的逻辑.承接扩展点之后,就是Spring容器的另一个核心:Bean的生命周期过程.这个生命周期过程大致经历了一下的几个 ...
- Spring源码系列 — BeanDefinition
一.前言 回顾 在Spring源码系列第二篇中介绍了Environment组件,后续又介绍Spring中Resource的抽象,但是对于上下文的启动过程详解并未继续.经过一个星期的准备,梳理了Spri ...
- 事件机制-Spring 源码系列(4)
事件机制-Spring 源码系列(4) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostProcess ...
- Ioc容器依赖注入-Spring 源码系列(2)
Ioc容器依赖注入-Spring 源码系列(2) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostPr ...
- Ioc容器BeanPostProcessor-Spring 源码系列(3)
Ioc容器BeanPostProcessor-Spring 源码系列(3) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Io ...
- Ioc容器beanDefinition-Spring 源码系列(1)
Ioc容器beanDefinition-Spring 源码系列(1) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器 ...
- Spring源码系列(二)--bean组件的源码分析
简介 spring-bean 组件是 Spring IoC 的核心,我们可以使用它的 beanFactory 来获取所需的对象,对象的实例化.属性装配和初始化等都可以交给 spring 来管理. 本文 ...
随机推荐
- DAO层,Service层,Controller层、View层、entity层
1.DAO(mapper)层:DAO层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此,DAO层的设计首先是设计DAO的接口,然后在Spring的配置文件中定义此接口的实现类,然后就 ...
- mac安装conda后,终端的用户名前面有一个(base),最佳解决方案
mac安装了conda后,前面会有一个(base),很烦人,终于找到最佳解决方案了: $ conda config --set auto_activate_base false 原因: 安装conda ...
- #企业项目实战 .Net Core + Vue/Angular 分库分表日志系统六 | 最终篇-通过AOP自动连接数据库-完成日志业务
教程预览 01 | 前言 02 | 简单的分库分表设计 03 | 控制反转搭配简单业务 04 | 强化设计方案 05 | 完善业务自动创建数据库 06 | 最终篇-通过AOP自动连接数据库-完成日志业 ...
- LCA详解
LCA,即最近公共祖先,在图论中应用比较广泛. LCA的定义如下:给定一个有根树,若节点$z$同时是节点$x$和节点$y$的祖先,则称$z$是$x,y$的公共祖先:在$x,y$的所有公共祖先当中深度最 ...
- Ubuntu 20.04.1 安装软件和系统配置脚本
#!/bin/bash # https://launchpad.net/ubuntu # https://www.easyicon.net # https://download-chromium.ap ...
- Docker 私有镜像仓库的搭建及认证
DockerHub 为我们提供了很多官方镜像和个人上传的镜像,我们可以下载机构或个人提供的镜像,也可以上传我们自己的本地镜像,但缺点是: 由于网络的原因,从 DockerHub 下载和上传镜像速度可能 ...
- HTTP系列:缓存
先看一些概念性的术语: 命中率:由缓存提供服务的请求所占的比例被称为缓存命中率: 缓存未命中:其实就是一些到达缓存的请求没有副本可用,而被转发给原始服务器: 再验证:原始服务器上内容可能会发生变化,缓 ...
- Mac 安装多个版本jdk
JDK默认安装路径为/Library/Java/JavaVirtualMachines 多版本安装后效果为: 设置 1.执行以下命令 cd ~ open -e .bash_profile #打开.ba ...
- BM算法学习
根据阮一峰大大的文章实现,不过没实现“搜索词中的上一次出现位置”(我直接实时查找,显然应该预处理): 文章:http://www.ruanyifeng.com/blog/2013/05/boyer-m ...
- Java获取Linux上指定文件夹下所有第一级子文件夹
说明:需要只获得第一级文件夹目录 package com.sunsheen.jfids.studio.monitor.utils; import java.io.BufferedReader; imp ...