Spring源码学习(五):Spring AOP

免责声明

本人还处于学习阶段,如果内容有错误麻烦指出,敬请见谅!!!

Demo

在这里插入图片描述

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.8</version>
</dependency>

AOP标签解析源码

​   其实AOP的标签解析与component-scan的逻辑一样,都会走parseCustomElement方法:
在这里插入图片描述

​   进入parseCustomElement方法后,解析aop标签的步骤和解析component-scan标签的步骤就一模一样,先是得到命名空间处理器(handler),再是得到aop标签的解析器(parser),然后调用解析器的parse方法完成标签解析。

​   由于之前在component-scan详细说了handler和parser的获取过程,这里就简单提一下:
在这里插入图片描述

在这里插入图片描述

component-scan: ContextNamespaceHandler->ComponentScanBeanDefinitionParser
aop: AopNamespaceHandler->ConfigBeanDefinitionParser

​   让我们再进一步看看ConfigBeanDefinitionParserparse方法:

在这里插入图片描述

1.configureAutoProxyCreator干了什么

​   尝试向容器注入或者升级AspectJAwareAdvisorAutoProxyCreator类型的自动代理创建者bean定义,专门用于后续创建AOP代理对象,注意这里是bean定义嗷,并没有实例化。

​   configureAutoProxyCreator首先会调用AopNamespaceUtils工具类的registerAspectJAutoProxyCreatorIfNecessary方法:

在这里插入图片描述

1.1 registerAspectJAutoProxyCreatorIfNecessary

​   调用AopConfigUtilsregisterAspectJAutoProxyCreatorIfNecessary方法,通过方法名也很好理解,该方法的作用是如果需要的话注册一个代理对象创建者。该方法会进一步调用registerOrEscalateApcAsRequired方法,这里面才是真正创建代理创建者的逻辑。

​   registerOrEscalateApcAsRequired方法的底层逻辑是这样的:

1.在beanFactory中找有没有beanName为"org.springframework.aop.config.internalAutoProxyCreator"的beanDefinition,有的话就尝试升级。
2.没有的话创建AspectJAwareAdvisorAutoProxyCreator的beanDefiniton,并以"org.springframework.aop.config.internalAutoProxyCreator"为beanName存入beanFactory中

在这里插入图片描述

1.1.1 不同代理创建者的优先级是什么(findPriorityForClass)

​   从上述代码可以看到,如果beanFactory缓存命中,则会去比较当前类型的代理创建者和beanFactory中类型的代理创建者的优先级,findPriorityForClass方法的实现也蛮朴实无华的:

在这里插入图片描述

​   从registerAspectJAutoProxyCreatorIfNecessary方法中我们可以看到,我们指定的代理创建者类型是AspectJAwareAdvisorAutoProxyCreator,其实还有其他:

1.< tx:annotation-driven />标签或者@EnableTransactionManagement事物注解:InfrastructureAdvisorAutoProxyCreator.class;
2.< aop:config />标签:AspectJAwareAdvisorAutoProxyCreator.class;
3.< aop:aspectj-autoproxy />标签或者@EnableAspectJAutoProxy注解:AnnotationAwareAspectJAutoProxyCreator.class。

​   从findPriorityForClass实现逻辑来看,这三者的优先级是:

AnnotationAwareAspectJAutoProxyCreator>AspectJAwareAdvisorAutoProxyCreator>InfrastructureAdvisorAutoProxyCreator

​   这三个也有相同的父类:

在这里插入图片描述

1.2 useClassProxyingIfNecessary干了什么

​   在升级或者注册了自动代理创建者的bean定义之后,这一步用于解析、设置proxy-target-class和expose-proxy属性到这个bean定义的proxyTargetClassexposeProxy属性中。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

1.3 registerComponentIfNecessary干了什么

​   还记得我们在parse方法最开始的时候创建了一个CompositeComponentDefinition类型的对象吗,这里我们就用上了,将创建代理对象的beanDefiniton封装进BeanComponentDefinition中,然后再存入CompositeComponentDefinition里面。

在这里插入图片描述

2 parsePointcut方法解析< aop:pointcut/>切入点标签

​   parsePointcut方法干的事情其实挺好理解的,方法内部根据< aop:pointcut/>标签创建一个beanDefinition,然后将这个beanDefinifition存入beanFactory中,最后将beanDefinition封装进PointcutComponentDefinition,然后进行注册(其实就是放入我们再事先创建好的CompositeComponentDefinition)。

在这里插入图片描述

2.1 createPointcutDefinition

​   createPointcutDefinition方法主要是根据< aop:pointcut/>标签的expression属性创建一个RootBeanDefinition类型的beanDefinition,该beanDefinition对应的class为AspectJExpressionPointcut

在这里插入图片描述

3. parseAdvisor解析<aop:advisor/>通知器标签

​   < aop:advisor/>通知器标签,它和< aop:aspect >类似,相当于一个小切面,它的advice-ref属性可以指向一个通知类bean,该bean要求实现Advice相关接口,不需要单独配置通知,但接口只有前置通知、后置通知和异常通知的方法。

​   通常,advisor被用来管理事物,它的advice-ref属性配置对一个< tx:advice >的id引用,后面学习事物的时候就知道了。

​   类似的,parseAdvisor方法也会生成一个beanDefinition然后存入beanFactory中,也会注册进CompositeComponentDefinition中:

在这里插入图片描述

3.1 createAdvisorBeanDefinition

​   让我们看看advisor标签对应的beanDefinition:

在这里插入图片描述

3.2 parsePointcutProperty

​   parsePointcutProperty方法主要是获取切入点的信息:

在这里插入图片描述

4. parseAspect解析< aop:aspect/>切面标签

​   < aop:aspect/>标签用于配置切面,其内部可以定义具体的应用到哪些切入点的通知,这是一个非常重要的标签,相比其他两个标签,parseAspect方法稍微有些复杂:

在这里插入图片描述

在这里插入图片描述

4.1 parseAdvice:解析通知标签

​   让我们继续看看parseAdvice方法,该方法主要是用来解析通知标签,包括< aop:before/>、< aop:after/>、< aop:after-returning/>、< aop:after-throwing/>、< aop:around/>,解析的最终bean定义同样会注入到容器中:

在这里插入图片描述

​   可以看到parseAdvice方法创建了一系列的beanDefinition,我们结合Xml配置文件看看,parseAdvice方法干了什么事情:

<aop:aspect id="aspect" ref="aspectMethod">
     <aop:before method="aspect" pointcut-ref="pointcut"/>
     <aop:after method="aspect" pointcut-ref="pointcut"/>
     <aop:after method="aspect" pointcut-ref="pointcut"/>
</aop:aspect>
  1. 首先会创建MethodLocatingFactoryBean类型的beanDefinition,并向该beanDefinition中添加属性methodName,其值为method属性的值。添加targetBeanName属性,其值为父标签aspect的ref属性的值。(内部bean,不会注册到beanFactory中)
  2. 创建SimpleBeanFactoryAwareAspectInstanceFactory类型的beanDefinition,并添加aspectBeanName属性,其值为父标签aspect的ref属性的值。SimpleBeanFactoryAwareAspectInstanceFactory是一个实例工厂,专门用于获取切面实例对象,也就是通知类对象。(内部bean,不会注册到beanFactory中)
  3. 将上面两个beanDefiniton作为参数传入createAdviceDefinition中,该方法返回的也是一个beanDefinition,表示advice通知标签的beanDefinition,也就是< aop:before/>、< aop:after/>对应的beanDefinition。(内部bean,不会注册到beanFactory中)
  4. 再创建一个AspectJPointcutAdvisor类型的beanDefinition,这是切入点通知器bean定义,把第三步中的beanDefinition作为构造器参数添加至bean定义中。(注册到beanFactory中)
4.1.1 createAdviceDefinition创建通知bean定义

​   createAdviceDefinition方法内部会根据不同通知的类型创建不同类型的beanDefinition,然后设置beanDefinition中的属性值。特殊的是,方法内部还会设置构造器参数:

在这里插入图片描述

​   我们进入到getAdviceClass方法内部可以看到,不同通知类型对应着不同类型的class:

在这里插入图片描述

​   从类的继承关系来看,这五个通知类型都继承至AbstractAspectJAdvice

在这里插入图片描述

​   其实这五个子类的构造方法都是调用父类的构造方法,因此这五个通知类型对应的class不一样,但是它们的构造器参数却是一样的:

在这里插入图片描述


AspectJAwareAdvisorAutoProxyCreator创建代理对象

​   之前我们已经学习了aop标签解析的相关源码,标签解析主要干了两件事:首先创建了一个创建代理对象的对象(AspectJAwareAdvisorAutoProxyCreator)主要是用来后面创建代理对象的,然后就是解析aop的子标签,创建并注册相关的beanDefinition。

​   接下来我们具体来看看,Spring是如何用AspectJAwareAdvisorAutoProxyCreator创建代理对象的。

写在前面

​   我们先来看看AspectJAwareAdvisorAutoProxyCreator实现了哪些接口,可以看到这些接口在我们Spring初始化启动源码分析时都有讲到过,特别是BeanPostProcessor这个超级接口,我们会回调里面的方法干很多事情。

在这里插入图片描述

​   让我们来稍微回顾一下Spring初始化的流程,主要是看Spring对后处理器的处理流程:

在这里插入图片描述

1. postProcessBeforeInstantiation方法

​   实际上是执行AspectJAwareAdvisorAutoProxyCreator的父类AbstractAutoProxyCreator的方法:

在这里插入图片描述

1.1 getCacheKey:根据beanName和beanClass获取cacheKey

​   getCacheKey方法实现比较简单,先判断beanName是否为空,不为空的话再判断是不是FactoryBean,是FactoryBean则返回&+beanName,不是FactoryBean则返回beanName。

​   如果beanName为空,则返回beanClass。

在这里插入图片描述

1.2 isInfrastructureClass:是否是Spring AOP基础结构类

​   如果当前beanClass是Spring AOP的基础结构类,则isInfrastructureClass返回true,不进行代理。默认实现是将Advice、Pointcut、Advisor、AopInfrastructureBean接口及其实现均作为AOP基础结构类。

在这里插入图片描述

1.3 shouldSkip:是否跳过代理

​   该方法在AspectJAwareAdvisorAutoProxyCreator中的逻辑如下:

在这里插入图片描述

​   该方法在父类AbstractAutoProxyCreator中的逻辑如下:如果beanName以beanCassName开头,并且以“.ORIGINAL”结束,那么返回true,否则返回false。

在这里插入图片描述

1.4 getCustomTargetSource:获取自定义目标源

​   获取当前bean的自定义目标源TargetSource。如果获取到了TargetSource,那么将会直接在postProcessBeforeInstantiation方法中创建代理,避免没必要的Spring初始化和实例化流程,同时后面的postProcessAfterInitialization方法中不会再创建代理对象。

​   但是通常情况下我们并未设置TargetSource,因此不会走这个逻辑创建代理对象,创建代理对象的逻辑还是在postProcessAfterInitialization中。

1.5 postProcessBeforeInstantiation方法的调用时机

​   当我们把AspectJAwareAdvisorAutoProxyCreator的beanDefinition加载到beanFactory中,并实例化后,我们再来看看createBean方法的部分逻辑:

在这里插入图片描述

​   让我们继续进到resolveBeforeInstantiation方法里面看看,很明显重点在于applyBeanPostProcessorsBeforeInstantiationapplyBeanPostProcessorsAfterInitialization方法,这方法内部分别会回调postProcessBeforeInstantiationpostProcessAfterInstantiation方法:

在这里插入图片描述

​   值得注意的是AspectJAwareAdvisorAutoProxyCreator后处理器属于InstantiationAwareBeanPostProcessorapplyBeanPostProcessorsBeforeInstantiation里面会回调postProcessBeforeInstantiation方法,在这个方法内部会缓存advisedBean。因此advisedBean实例化时,会在这里被缓存到AspectJAwareAdvisorAutoProxyCreator后处理器里面。

​   但是由于不会设置TargetSource,因此这里返回的bean通常为null,不会进一步执行applyBeanPostProcessorsAfterInstantiation方法。

2. postProcessAfterInitialization方法

​   postProcessAfterInitialization包含了创建代理对象的核心方法,核心逻辑在wrapIfNecessary里面:

在这里插入图片描述

2.1 wrapIfNecessary方法

​   在文章开头给出的案例中,对于AspectTarget会执行到第四步,进行代理对象的创建:

在这里插入图片描述

2.1.1 getAdvicesAndAdvisorsForBean:获取可用Advisors

​   获取当前bean(本案例中指的是AspectTarget)可用的Advisor通知器,该方法由子类实现。在解析**< aop:config/>标签时,< aop:advisor/>标签的DefaultBeanFactoryPointcutAdvisor< aop:declare-parents/>**标签的DeclareParentsAdvisor,通知标签的AspectJPointcutAdvisor,他们都属于Advisor类型,也就是通知器,通常一个切入点和一个通知方法就组成通知器。

​   在本案例中其实主要的Advisor就是通知标签的AspectJPointcutAdvisor

在这里插入图片描述

​   可以看到getAdvicesAndAdvisorsForBean方法内部的核心方法是findEligibleAdvisors来获得当前bean的可用通知器Advisors,我们继续来看看findEligibleAdvisors方法的具体实现:

在这里插入图片描述

2.1.1.1 findAdvisorsThatCanApply:找到可应用当前bean的Advisor

​   findAdvisorsThatCanApply主要是来找到可以应用到当前bean的Advisor,会分别对**< aop:declare-parents/>**和通知标签做处理,判断是否可用于当前bean也很简单,根据切入点表达式来对类和方法进行匹配即可:在这里插入图片描述

2.1.1.2 extendAdvisors:扩展通知器链

​   添加一个特殊的Advisor到Advisors链头部,使用 AspectJ 切入点表达式和使用 AspectJ 样式的Advice时都需要添加,添加的是ExposeInvocationInterceptor.ADVISOR,实际类型是一个DefaultPointcutAdvisor类型的单例实例,它内部保存了ExposeInvocationInterceptor拦截器的单例实例,当进行拦截时Advisors链的第一个方法就是调用该拦截器的invoke方法。

​   在本案例中,执行完这一步后,对于AspectTarget的Advisor链,应该有四个Advisor(3个通知Advisor+1个特殊Advisor)。

在这里插入图片描述

2.1.2 createProxy:创建代理对象

​   为给定的 bean 创建 AOP 代理。这一步会选择到底是通过JDK代理创建还是通过CGLIB代理创建。

在这里插入图片描述

2.1.2.1 evaluateProxyInterfaces:是否有合理接口

​   如果当前接口不是一个容器回调接口,并且当前接口不是内部语言接口,并且接口方法个数至少为1个(不是标志性接口)。同时满足上面三个条件,当前接口就是一个合理的代理接口。

​   在本案例中,因为AspectTarget没有合理接口,因此会在这一步将proxyTargetClass属性设置为true,最后会走CGLIB代理模式。

在这里插入图片描述

2.1.2.2 getProxy:获取代理对象

​   获取代理对象分为两步:首先我们需要创建对应类型的AopProxy(JDK or CGLIB),然后再根据AopProxy获取代理对象。

在这里插入图片描述

​   我们先看看createAopProxy方法,Spring最终会调用DefaultAopProxyFactorycreateAopProxy方法:

在这里插入图片描述

​   当我们得到了AopProxy,我们可以进一步获取代理对象,根据AopProxy的不同,获取代理对象的方式也不同。

​   我们先看看JDK代理模式获取代理对象:

在这里插入图片描述

​   再看看CGLIB代理模式获取代理对象,稍微复杂一点:

在这里插入图片描述

​   再看看createProxyClassAndInstance方法:

在这里插入图片描述

代理对象的增强方法的执行原理

写在前面

​   还是以文章开头的Demo为例,我们获取目标类(AspectTarget)的Bean时,Spring容器会返回给我们AspectTarget的代理对象:

在这里插入图片描述

​   可以看到,我们的Demo最终还是走的是CGLIB代理模式,由于JDK和CGLIB在方法增强的逻辑其实差不多,我们就简单分析一下CGLIB执行的原理,其基本思想是:责任链模式和方法递归调用来实现方法的代理和增强的逻辑。

增强入口:DynamicAdvisedInterceptor的intercept方法

​   进一步地,获取到代理对象后,我们调用目标类的target方法,最终会被第一个拦截器DynamicAdvisedInterceptor拦截,调用里面的intercept方法:

在这里插入图片描述

​   以文章开头的Demo为例,现在代理对象有四个Advisor(一个特殊+三个通知),特殊Advisor会匹配所有方法,三个通知Advisor匹配目标类的target方法,我们看看chain中有哪些东西:

在这里插入图片描述

增强逻辑:CglibMethodInvocation

​   在找到匹配当前方法的Advisor后,我们继续看看增强方法的调用逻辑,首先来看看CglibMethodInvocationproceed方法,其实是调用了父类ReflectiveMethodInvocationproceed方法:

在这里插入图片描述

​   我们看看Demo中对应的三个类型的拦截器的invoke方法:

在这里插入图片描述

​   我们要明白两件事,首先是tryreturnfinally的执行顺序:

try中的代码块(包括return语句中的代码,但是不执行return返回操作)> finally代码块 > return操作

​   其次是

mi.proceed()最后执行的还是ReflectiveMethodInvocation的proceed方法

​   让我们来看看Demo中的递归栈变化和增强方法的执行:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/909607.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【全面解析】Stable Diffusion AI绘画入门教程,轻松掌握,让绘画新手也能快速上手!

前言&#xff1a; 随着人工智能技术的飞速发展&#xff0c;AI绘画领域迎来了一场革命。StableDiffusion作为一款强大的AI绘画工具&#xff0c;以其稳定的图像生成能力和卓越的创造力赢得了广泛关注。本文将向您介绍StableDiffusion的核心特点及其在绘画创作中的广泛应用。同时…

stm32使用串口的轮询模式,实现数据的收发

------内容以b站博主keysking为原型&#xff0c;整理而来&#xff0c;用作个人学习记录。 首先在STM32CubeMX中配置 前期工作省略&#xff0c;只讲重点设置。 这里我配置的是USART2的模式。 会发现&#xff0c;PA2和PA3分别是TX与RX&#xff0c;在连接串口时需要TX对RX&…

openapi回调地址请求不通过

目录 1. 验证url接口get请求本地自测报错 2. 测试回调模式成功不返回结果 3. 测试回调模式返回结果带双引号 对接企业微信 产生会话回调事件 接口问题解决 1. 验证url接口get请求本地自测报错 java.lang.IllegalArgumentException: Last encoded character (before the pa…

软件设计师笔记-数据结构

数据结构 数据元素的集合及元素间的相互关系和构造方法。 线性表的存储结构 顺序存储链式存储 单链表节点 typedef struct node { int data; struct node *link; }NODE, *LinkList; 双向链表 每个节点有两个指针&#xff0c;分别指出直接前驱和直接后继。 循环链表 尾…

【javascript】console 对象提供的方法

文章目录 1、 console.dir() 打印对象2、console.table() 打印数组3、 console.clear() 清理控制台4、console.group() 控制打印组5、console.time() 完成计时 console.log 是一个很好的调试方式。但是 如果我们滥用它&#xff0c;效果反而会适得其反&#xff01;大量打印信息堆…

一:时序数据库-Influx应用

目录 0、版本号 1、登录页面 2、账号基本信息 3、数据库案例 4、可视化 5、java案例 0、版本号 InfluxDB v2.4.0 1、登录页面 http://127.0.0.1:8086/signin 账号&#xff1a;自己账号 密码&#xff1a;自己密码 2、账号基本信息 查看用户id和组织id&#xff01;&…

构建一个导航栏web

<!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><style>*{margin: 0;padding: 0;}#menu{background-color:purple;width: 100px;height: 50px;}.item{float: left;/* 浮动标签可以让块标签&#xff0c…

JAVA基础:单元测试;注解;枚举;网络编程 (学习笔记)

单元测试 操作步骤&#xff1a; a.导包import org.junit; b.三个注解 Test Before After c.点击Test 运行就可以了 用在不需要控制台输入的情境下&#xff1a;javaweb&#xff0c;框架项目&#xff0c;微服务项目 供开发人员自己做测试。 package com.page…

Node.js-增强 API 安全性和性能优化

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;node.js篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来node.js篇专栏内容:node.js-增强 API 安全性和性能优化 前言 在前几篇文章中&#xff0c;我们已经构建了一个…

ThingsBoard规则链节点:Push to Edge节点详解

引言 1. Push to Edge 节点简介 2. 节点配置 2.1 基本配置示例 3. 使用场景 3.1 边缘计算 3.2 本地数据处理 3.3 实时响应 4. 实际项目中的应用 4.1 项目背景 4.2 项目需求 4.3 实现步骤 5. 总结 引言 ThingsBoard 是一个开源的物联网平台&#xff0c;提供了设备管…

第二届计算机网络技术与电子信息工程国际学术会议(CNTEIE 2024,12月6-8日)

第二届计算机网络技术与电子信息工程国际学术会议&#xff08;CNTEIE 2024&#xff09; 2024 2nd International Conference on Computer Network Technology and Electronic and Information Engineering 官方信息 会议官网&#xff1a;www.cnteie.org 2024 2nd Internation…

A013-基于SpringBoot的宽带业务管理系统的设计与实现

&#x1f64a;作者简介&#xff1a;在校研究生&#xff0c;拥有计算机专业的研究生开发团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339; 赠送计算机毕业设计600…

「C/C++」C/C++标准库 之 #include<cstddef> 常用定义和宏

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「C/C」C/C程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasoli…

Vue3-子传父

1. 主组件 App.vue&#xff08;父组件&#xff09; 在 App.vue 中&#xff0c;我们先引入了子组件 SonCom&#xff0c;这个小家伙将在父组件中出场。 接着&#xff0c;我们写了一个叫 getMessage 的函数。这个函数的任务很简单——接收子组件传来的消息&#xff0c;然后用 con…

网络--传输层协议--TCP

1、TCP协议的特性 面向连接(需要建立连接,才能继续通信) 面向字节流的一对一通信模式 通过流量控制和拥塞控制 -> 确保可靠传输 报头大小20字节(若带选项,最大60字节) 2、TCP报文字段 16位源端口号 和 16位目的端口号 -- 2 + 2 = 4 字节 32位序号 和 32位确认序…

【Python】计算机视觉应用:OpenCV库图像处理入门

计算机视觉应用&#xff1a;OpenCV库图像处理入门 在当今的数字化时代&#xff0c;计算机视觉&#xff08;Computer Vision&#xff09;已经渗透到各行各业&#xff0c;比如自动驾驶、智能监控、医疗影像分析等。而 Python 的 OpenCV 库&#xff08;Open Source Computer Visi…

【Ai测评】GPT Search偷偷上线,向Google和微软发起挑战!

最近&#xff0c;OpenAI 又推出了一个令人兴奋的新功能——GPT Search&#xff0c;已经正式上线了&#xff01; 功能介绍 GPT Search&#xff1a;为你带来全新搜索体验 目前&#xff0c;桌面端和移动端应用程序已经全面上线&#xff0c;所有 GPT Plus 和 Team 用户都可以立即…

iOS用rime且导入自制输入方案

iPhone 16 的 cantonese 只能打传统汉字&#xff0c;没有繁简转换&#xff0c;m d sh d。考虑用「仓」输入法 [1] 使用 Rime 打字&#xff0c;且希望导入自制方案 [2]。 仓输入法有几种导入方案的方法&#xff0c;见 [3]&#xff0c;此处记录 wifi 上传法。准备工作&#xff1…

【数据结构】算法的时间复杂度和空间复杂度

写在前面 在我们学习数据结构之前&#xff0c;我们肯定就听过某某大神说&#xff0c;我的排序算法的时间复杂度只需要O(logN)&#xff0c;空间复杂度只需要O(1)…听的好唬人&#xff0c;但是实际也真牛。那其中时间复杂度和空间复杂度是什么呢&#xff1f;不才这篇笔记就深入讲…

基于MATLAB的农业病虫害识别研究

matlab有处理语音信号的函数wavread&#xff0c;不过已经过时了&#xff0c;现在处理语音信号的函数名称是audioread选取4.wav进行处理&#xff08;只有4的通道数为1&#xff09; 利用hamming窗设计滤波器 Ham.m function [N,h,H,w] Ham(fp,fs,fc)wp 2*pi*fp/fc;ws 2*pi*…