当前位置: 首页 > news >正文

【23.12.30期--Spring篇】Spring的AOP介绍(详解)

在这里插入图片描述

Spring的AOP介绍

  • ✔️简述
  • ✔️扩展知识
    • ✔️AOP是如何实现的


✔️简述


AOP(Aspect-Oriented Programming),即面向切面编程,用人话说就是把公共的逻辑抽出来,让开发者可以更专注于业务逻辑开发。


和IOC-样,AOP也指的是一种思想。AOP思想是OOP (Obiect-Oriented Programming) 的补充OOP是面向类和对象的,但是AOP则是面向不同切面的。一个切面可以横跨多个类和对象去操作,极大的丰富了开发者的使用方式,提高了开发效率。


譬如,一个订单的创建,可能需要以下步骤:


1.权限校验
2.事务管理
3.创建订单
4.日志打印


如果使用AOP思想,我们就可以把这四步当成四个“切面”,让业务人员专注开发第三个切面,其他二个切面则是基础的通用逻辑,统一交给AOP封装和管理。


Spring AOP有如下概念(列举下,不用刻意记):


术语翻译释义
Aspect切面切面由切入点和通知组成,它既包含了横切逻辑的定义,也包括了切入点的定义。切面是一个横切关注点的模块化,一个切面能够包含同一个类型的不同增强方法,比如如说事务处理和日志处理可以理解为两人切面。
PointCut切入点切入点是对连接点进行拦截的条件定义,决定通知应该作用于截哪些方法。 (充当where角色,即在哪里做)
Advice通知通知定义了通过切入点拦截后,应该在连接点做什么,是切面的具体行为。 (充当what角色,即做什么)
Target目标对象目标对象指将要被增强的对象,即包含主业务逻辑的类对象。或者说是被一个或者多个切面所通知的对象。
JoinPoint连接点连接点是程序在运行时的执行点,这个点可以是正在执行的方法,或者是正在抛出的异常。因为Spring只支持方法类型的连接点,所以在Spring中连接点就是运行时刻被拦截到的方法。连接点由两个信息确定: 1 . 方法(表示程序执行点,即在哪个目标方法) 2 . 相对点(表示方位,即目标方法的什么位置,比如调用前,后等)
Weaving织入织入是将切面和业务逻辑对象连接起来,并创建通知代理的过程。织入可以在编译时,类加载时和运行时完成。在编译时进行织入就是静态代理,而在运行时进行织入则是动态代理。

对于通知类型来说:


以下是一个2列5行的表格:

术语释义
Before Advice连接点执行前执行的逻辑
After returning advice连接点正常执行(未抛出异常) 后执行的逻辑
After throwing advice连接点抛出异常后执行的逻辑
After finally advice无论连接点是正常执行还是抛出异常,在连接点执行完毕后执行的逻辑
Around advice该通知可以非常灵活的在方法调用前后执行特定的逻辑

✔️扩展知识


✔️AOP是如何实现的


SpringBean的初始化流程下一篇会单独做详细说明。


从Bean的初始化流程中来讲,Spring的AOP会在bean实例的实例化已完成,进行初始化后置处理时创建代理对象,即下面代码中的applyBeanPostProcessorsAfterlnitialization部分。


protected Object initializeBean(final String beanlame, final Object bean, RootBeanDefinition mbd) {//....//检查AwareinvokeAwareMethods(beanName , bean);//调用BeanPostProcessor的前置处理方法Object wrappedBean = bean;if (mbd == null  || !mbd.isSynthetic())  {wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanlame);}//调用InitializingBean的afterPropertiesSet方法或自定义的初始化方法及自定义init-method方法try {invokeInitMethods(beanName,wrappedBean, mbd);} catch (Throwable ex) {throw new BeanCreationException((mbd != null ? mbd.getResourceDescription() : null),beanName,"Invocation of init method failed", ex);}//调用BeanPostProcessor的后置处理方法if (mbd == null || !mbd.issynthetic()) {wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanlame);}return wrappedBean;
}

applyBeanPostProcessorsAfterlnitialization中会遍历所有BeanPostProcessor,然后调用其postProcessAfterlnitialization方法,而AOP代理对象的创建就是在AbstractAutoProxyCreator这个类的postProcessAfterlnitialization口中:


@override
public Object postProcessAfterInitialization(0bject bean, String beanlame) throws BeansException {if ((bean != null) {Object cacheKey = getCacheKey(bean.getClass(),beanName);if (this.earlyProxyReferences.remove(cacheKey) != bean) {if (this.earlyProxyReferences.remove(cacheKey) != bean) ;}}return bean;
}

这里面最重要的就是wrapIfNecessary方法了:


/**
*    如果需要,对bean进行包装。
*    
* 	@param bean 要包装的目标对象
* 	@param beanName bean的名称
* 	@param cacheKey 缓存键
* 	 @return 包装后的对象,可能是原始对象或代理对象
*/
protected Object wrapIfNecessary(Object bean, String beanlame, Object cachekey) {//如果beanName不为nul1且在目标源bean集合中,则直接返回原始对象if (beanName != null && this.targetSourcedBeans.contains(beanName)) {return bean;}//如果缓存键对应的值为 Boolean.FALSE,则直接返回原始对象if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}// 如果bean的类型为基础设施类,或者应跳过该类型的代理,则将缓存键对应的值设置为Boolean.FALSE并返回原始对象if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey Boolean.FALSE);return bean;}//如果存在advice,为bean创建代理对象Objectl] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanlame, null);if (specificInterceptors != DO NOT PROXY) {//将缓存键对应的值设置为 Boolean.TRUEthis .advisedBeans.put(cacheKey, Boolean.TRUE);//创建代理对象Object proxy = createProxy(bean.getClass(), beanlame, specificInterceptors, new SingletonTargetSource(bean));//将代理对象的类型与缓存键关联起来this .proxyTypes.put(cacheKey,proxy.getClass());return proxy;}// 如果没有advice,将缓存键对应的值设置为Boolean.FALSE并返回原始对象this.advisedBeans.put(cacheKey,Boolean.FALSE);return bean;
}

createProxy的主要作用是根据给定的bean类,bean名称、特订拦截器和目标源,创建代理对象:


/**
*    根据给定的bean类、bean名称、特定拦截器和目标源,创建代理对象。
*     
*     @param beanClass 要代理的目标对象的类
*     @param beanName bean的名称
*     @param specificInterceptors   特定的拦截器数组
*     @param targetSource   目标源
*     @return 创建的代理对象
*/
protected Object createProxy(Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource)  {//如果beanFactory是ConfigurableListableBeanFactory的实例,将目标类暴露给它if (this.beanFactory instanceof ConfigurableListableBeanFactory) {AutoProxyUtils.exposeTargetClass((ConfigurablelistableBeanFactory) this.beanFactory, beanllame, beanClass);}// 创建ProxyFactory实例,并从当前代理创建器复制配置ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.copyFrom(this);//如果不强制使用CGLIB代理目标类,根据条件决定是否使用CGLIB代理if (!proxyFactory.isProxyTargetClass())  {if (!proxyFactory.isProxyTargetClass()) {proxyFactory.setProxyTargetClass(true);} else {//根据bean类评估代理接口evaluateProxyInterfaces(beanClass,proxyFactory);}}// 构建advisor数组Advisor[] advisors = buildAdvisors(beaname, specificInterceptors);//将advisors添加到ProxyFactory中proxyFactory.addAdvisors(advisors);//设置目标源proxyFactory.setTargetSource(targetSource);//设置目标源proxyFactory.setTargetSource(targetSource);// 设置代理是否冻结proxyFactory.setFrozen(this.freezeProxy);//如果advisors已经预过滤,则设置ProxyFactory为预过滤状态if (advisorsPreFiltered())  {proxyFactory.setPreFiltered(true);}//获取代理对象,并使用指定的类加载器return proxyFactory.getProxy(getProxyClassLoader());}

Spring AOP 是通过代理模式实现的,具体有两种实现方式,一种是基于Java原生的动态代理种是基于cglib的动态代理。对应到代码中就是,这里面的Proxy有两种实现,分别是CglibAopProxy和JdkDynamicAopProxy。


Spring AOP默认使用标准的JDK动态代理进行AOP代理。这使得任何接口可以被代理。但是JDK动态代理有一个缺点,就是它不能代理没有接口的类。




所以SpringAOP就使用CGLIB代理没有接口的类。


在这里插入图片描述

当然代理这种设计模式也有动态代理和静态代理之分


参考链接: Java动态代理如何实现

相关文章:

  • 前端axios与python库requests的区别
  • chrome扩展程序开发之在目标页面运行自己的JS
  • python常见报错信息!错误和异常!附带处理方法
  • Spring Cloud - Eureka原理、注册、搭建、应用(全过程详解)
  • flask文件夹列表改进版--Bug追踪
  • 2023年新一代开发者工具 Vue ,正式开源!
  • Power BI - 5分钟学习合并文件
  • 【前端面经】即时设计
  • 通过数字证书对PDF电子文件进行数字签名/盖章
  • 【JavaWeb学习-第四章(1)】Ajax
  • QT编译并部署QtMqtt相关环境+跑测demo【超详细教程】
  • 【大语言模型】Transformer原理以及运行机制
  • 面向对象设计与分析40讲(17)双重检查锁定(double-checked locking)范式
  • PostgreSQL 作为向量数据库:入门和扩展
  • redhat 8 安装openstack
  • CSS 专业技巧
  • ES学习笔记(10)--ES6中的函数和数组补漏
  • JAVA之继承和多态
  • js 实现textarea输入字数提示
  • js如何打印object对象
  • js正则,这点儿就够用了
  • LeetCode29.两数相除 JavaScript
  • MD5加密原理解析及OC版原理实现
  • opencv python Meanshift 和 Camshift
  • vue-router 实现分析
  • webpack项目中使用grunt监听文件变动自动打包编译
  • 包装类对象
  • 机器学习 vs. 深度学习
  • 深度解析利用ES6进行Promise封装总结
  • 一个SAP顾问在美国的这些年
  • 在Docker Swarm上部署Apache Storm:第1部分
  • 正则表达式小结
  • puppet连载22:define用法
  • ​flutter 代码混淆
  • ​LeetCode解法汇总307. 区域和检索 - 数组可修改
  • # Panda3d 碰撞检测系统介绍
  • # 再次尝试 连接失败_无线WiFi无法连接到网络怎么办【解决方法】
  • %3cli%3e连接html页面,html+canvas实现屏幕截取
  • (1)STL算法之遍历容器
  • (Demo分享)利用原生JavaScript-随机数-实现做一个烟花案例
  • (未解决)macOS matplotlib 中文是方框
  • (新)网络工程师考点串讲与真题详解
  • (学习日记)2024.02.29:UCOSIII第二节
  • (一)ClickHouse 中的 `MaterializedMySQL` 数据库引擎的使用方法、设置、特性和限制。
  • (自用)learnOpenGL学习总结-高级OpenGL-抗锯齿
  • .NET Windows:删除文件夹后立即判断,有可能依然存在
  • .NET 使用 JustAssembly 比较两个不同版本程序集的 API 变化
  • .NET8.0 AOT 经验分享 FreeSql/FreeRedis/FreeScheduler 均已通过测试
  • .net反混淆脱壳工具de4dot的使用
  • .NET轻量级ORM组件Dapper葵花宝典
  • .pub是什么文件_Rust 模块和文件 - 「译」
  • /dev下添加设备节点的方法步骤(通过device_create)
  • @RequestBody的使用
  • [ vulhub漏洞复现篇 ] Apache APISIX 默认密钥漏洞 CVE-2020-13945
  • [AutoSar]BSW_Com02 PDU详解