Spring源码深度解析:十二、后处理器 BeanPostProcessor

news2024/11/19 15:27:00

一、前言

文章目录:Spring源码深度解析:文章目录

二、BeanPostProcessor

所谓的BeanPostProcessor翻译过来就是Bean后处理器。

1. 什么是 BeanPostProcessor

BeanPostProcessor是 Spring提供给我们的一个非常重要的扩展接口,并且Spring内部的很多功能也是通过 BeanPostProcessor来完成的(目前看到最典型的就是AnnotationAwareAspectJAutoProxyCreator的 注入)。

2. BeanPostProcessor 的种类

BeanPostProcessor 在Spring 中的子类非常多(idea 显是有46个),比如

  • InstantiationAwareBeanPostProcessorAdapter: 在Spring 的bean加载过程中起了非常重要的作用
  • AnnotationAwareAspectJAutoProxyCreatorbean创建过程中的 属性注入时起作用
  • AspectJAwareAdvisorAutoProxyCreator: Aspect 的 AOP 功能实现也全仰仗BeanPostProcessor的特性。

3. BeanPostProcessor 的创建

个人认为Bean的创建时可以认为分为两个过程: 一是Bean对应的BeanDefinition的创建。二是Bean实例的创建。

因为在 Spring容器中,Bean的创建并非仅仅通过反射创建就结束了,在创建过程中,需要考虑到Bean针对Spring容器中的一些属性,所以BeanDefinition中不仅仅包含了Bean Class文件信息,还包含了 当前Bean在Spring容器中的一些属性,比如在容器中的作用域、是否懒加载、别名等信息。当Bean进行实例化创建时需要依赖于对应的BeanDefinition提供对应的信息。

BeanDefinition的创建在 Spring 启动时对BeanFactoryPostProcessor的处理。
详参:
Spring源码深度解析:五、BeanFactoryPostProcessor的处理

Spring源码深度解析:六、ConfigurationClassPostProcessor

Bean 实例的创建在Spring 启动时的收尾工作
详参:
Spring源码深度解析:三、容器的刷新 - refresh()

而由于BeanPostProcessor是参与了Bean创建过程。所以其创建一定在普通Bean之前。实际上BeanPostProcessor的创建时在Spring启动时容器刷新的时候。

BeanPostProcessorBeanDefinition创建时机和普通Bean没有区别,都是在Spring 启动时的BeanFactoryPostProcessor中完成(确切的说是ConfigurationClassPostProcessor中完成)。

BeanPostProcessor的实例创建要优先于普通bean创建,Spring启动过程中会调用AbstractApplicationContext#registerBeanPostProcessors方法。 在这个方法中,Spring 会从容器中获取到所有BeanPostProcessor类型的beanName, 通过beanFactory.getBean方法获取到对应实例,进行排序后注册到BeanFactory.beanPostProcessors属性中,其中BeanFactory.beanPostProcessors的定义如下

	private final List<BeanPostProcessor> beanPostProcessors = new BeanPostProcessorCacheAwareList();

当容器需要执行 BeanPostProcessor方法时可以直接从beanPostProcessors中获取即可。

三、基本介绍

日常使用中,我们一般编写一个类来实现BeanPostProcessor或者 InstantiationAwareBeanPostProcessor接口,根据每个方法的调用时机,来完成响应的工作。
下面介绍一下接口方法,这里通过InstantiationAwareBeanPostProcessor接口来介绍 。InstantiationAwareBeanPostProcessorBeanPostProcessor的子接口,在 BeanPostProcessor基础上又扩展了三个方法。

@Component
public class DemoPostProcessor implements InstantiationAwareBeanPostProcessor {

	//InstantiationAwareBeanPostProcessor 提供的方法, 在 Class<T> -> T 的转换过程中
	// 此时bean还没创建,可以通过这方法代替 Spring 容器创建的方法
	@Override
	public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
		System.out.println("DemoPostProcesser.postProcessBeforeInstantiation   ###  1");
		return null;
	}

	//InstantiationAwareBeanPostProcessor 提供的方法, 返回的值代表是否需要继续注入属性,
	// 如果返回true,则会调用postProcessProperties和postProcessPropertyValues 来注入属性
	// 此时bean已经创建,属性尚未注入
	@Override
	public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
		System.out.println("DemoPostProcesser.postProcessAfterInstantiation   ###  2");
		return true;
	}

	//InstantiationAwareBeanPostProcessor 提供的方法,可以在这个方法中进行bean属性的注入, 这个方法已经过时,使用postProcessProperties 代理
	// 只有postProcessAfterInstantiation 返回true 时 且 postProcessProperties 返回 null 时调用
	@Override
	public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
		System.out.println("DemoPostProcesser.postProcessPropertyValues   ###  3");
		return pvs;
	}

	// BeanPostProcessor 提供的方法,在bean初始化前调用,这时候的bean大体已经创建完成了,在完成一些其他属性的注入
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("DemoPostProcesser.postProcessBeforeInitialization   ###  4");
		return bean;
	}
	// BeanPostProcessor 提供的方法,在bean初始化后调用,这时候的bean 已经创建完成了
	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("DemoPostProcesser.postProcessAfterInitialization   ###  5");
		return bean;
	}
}
@Configuration
@ComponentScan("com.wts.BeanPostProcessorTest")
public class SpringConfigPostProcessor {
}
public class Test {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfigPostProcessor.class);
		Object demoPostProcessor = applicationContext.getBean("demoPostProcessor");
		System.out.println(demoPostProcessor);
	}
}

在这里插入图片描述

四、源码中的调用场景

下面为了方便描述,进行一些简化写法,为了后面描述方便
BP : BeanPostProcessor
IBP : InstantiationAwareBeanPostProcessor
SBP : SmartInstantiationAwareBeanPostProcessor

其结构如下图:
在这里插入图片描述
这里就不具体贴出多少代码。简单解释一下调用场景

Spring 在启动过程中,会将所有实现了BeanPostProcessor接口的子类保存到 AbstractBeanFactory中的beanPostProcessors集合中,如下:

private final List<BeanPostProcessor> beanPostProcessors = new CopyOnWriteArrayList<>();

在适当的时候(这个适当的时候就根据 每个接口方法定义的意义来判断), Spring会获取所有的BeanPostProcessor子类集合,即beanPostProcessors,经过类型过滤后,调用对应的方法。比如,就是对InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation方法的调用流程(其余方法的调用也类似):
AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInstantiation()

	@Nullable
	protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
		// 获取所有BeanPostProcessor进行遍历
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			// 因为只有 InstantiationAwareBeanPostProcessor  类型才有postProcessBeforeInstantiation 方法,所以这里要判断一下是不是 InstantiationAwareBeanPostProcessor   类型
			if (bp instanceof InstantiationAwareBeanPostProcessor) {
				InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
				// 调用postProcessBeforeInstantiation方法
				Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
				if (result != null) {
					return result;
				}
			}
		}
		return null;
	}

1. InstantiationAwareBeanPostProcessor

下面来介绍在 Spring 创建流程中 每个方法的实际调用位置:

1.1. postProcessBeforeInstantiation

InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation方法在Bean创建之前调用,我所理解的目的是替换Spring 容器创建bean, 当InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation返回不为null时,则不会再通过Spring 创建bean,而是使用 InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation返回的bean

AbstractAutowireCapableBeanFactory#createBean()
在这里插入图片描述
在这里插入图片描述
resolveBeforeInstantiation方法内容如下,可以看到 当 applyBeanPostProcessorsBeforeInstantiation方法(applyBeanPostProcessorsBeforeInstantiation调用了postProcessBeforeInstantiation方法) 返回值不为 null,则会调用 applyBeanPostProcessorsAfterInitialization方法,从而调用postProcessAfterInitialization方法。因为这里bean返回不为null,则代表bean创建成功了,就会调用创建成功后的方法,即postProcessAfterInitialization

AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation()

	@Nullable
	protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
		Object bean = null;
		// 如果尚未被解析
		if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
			// Make sure bean class is actually resolved at this point.
			// 当前类并非合成类 && 存在 BeanPostProcessor (后处理器)
			if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
				// 1. 获取目标类
				Class<?> targetType = determineTargetType(beanName, mbd);
				// 2. 实例前的后处理器应用
				if (targetType != null) {
					bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
					if (bean != null) {
						// 3. 实例后的后处理器应用
						bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
					}
				}
			}
			mbd.beforeInstantiationResolved = (bean != null);
		}
		return bean;
	}

这里需要注意,在resolveBeforeInstantiation方法中,当bean != null时 调用了applyBeanPostProcessorsAfterInitialization方法,即 BeanPostProcessor.postProcessAfterInitialization方法。这是因为如果bean != null, 则说明 bean 的创建已经完成,那么这里则是最后调用bean 的后置处理的机会,即调用BeanPostProcessor.postProcessAfterInitialization方法的最后机会。

1.2. postProcessAfterInstantiation & postProcessPropertyValues

InstantiationAwareBeanPostProcessor.postProcessAfterInstantiationInstantiationAwareBeanPostProcessor.postProcessPropertyValues的调用场景只有一处,在AbstractAutowireCapableBeanFactory#populateBean()方法中,此时bean已经创建完成,正在进行属性注入。而InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation的返回值决定是否继续注入

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
		...
		// 调用 postProcessAfterInstantiation 方法吗,如果返回false,直接return;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof InstantiationAwareBeanPostProcessor) {
					InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
					if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
						return;
					}
				}
			}
		}

		... 
		if (hasInstAwareBpps || needsDepCheck) {
			if (pvs == null) {
				pvs = mbd.getPropertyValues();
			}
			PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
			if (hasInstAwareBpps) {
				// 3. 成员变量的注入
				// 调用了InstantiationAwareBeanPostProcessor.postProcessPropertyValues()方法,来进行设值后处理
				for (BeanPostProcessor bp : getBeanPostProcessors()) {
					if (bp instanceof InstantiationAwareBeanPostProcessor) {
						InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
						// 调用设值
						pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
						if (pvs == null) {
							return;
						}
					}
				}
			}
			// 如果需要检查
			if (needsDepCheck) {
				// 依赖检查,对应 depends-on属性,3.0 已弃用
				checkDependencies(beanName, mbd, filteredPds, pvs);
			}
		}
	...
	}

通过代码我们可以比较清楚的看到整体逻辑:

  1. InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation返回true,才会执行下面步骤
  2. 调用InstantiationAwareBeanPostProcessor.postProcessPropertyValues注入属性

2. BeanPostProcessor

2.1. postProcessBeforeInitialization

BeanPostProcessor.postProcessBeforeInitialization调用时机是bean已经创建完成,但是尚未初始化,即一些属性配置尚未完成(我看到的就一个init-method的配置)。使用场景也只有一处,即
AbstractAutowireCapableBeanFactory#initializeBean(String, Object, RootBeanDefinition)。

程序走到这里,代表resolveBeforeInstantiation方法中的 InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation并未代理bean的创建,在这里则是由Spring 创建的bean的时候来调用。

AbstractAutowireCapableBeanFactory#initializeBean()

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
		// 对特殊的bean进行处理: 实现了Aware、BeanClassLoaderAware、BeanFactoryAware的处理。
		if (System.getSecurityManager() != null) {
			AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
				// 激活Aware方法
				invokeAwareMethods(beanName, bean);
				return null;
			}, getAccessControlContext());
		}
		else {
			invokeAwareMethods(beanName, bean);
		}

		Object wrappedBean = bean;

		// 初始化前
		if (mbd == null || !mbd.isSynthetic()) {
			// 调用了bean前处理器的方法
			wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
		}

		// 初始化
		try {
			// 激活自定义的init的方法。
			invokeInitMethods(beanName, wrappedBean, mbd);
		}
		catch (Throwable ex) {
			throw new BeanCreationException(
					(mbd != null ? mbd.getResourceDescription() : null),
					beanName, "Invocation of init method failed", ex);
		}

		// 初始化后
		if (mbd == null || !mbd.isSynthetic()) {
			// 调用bean后处理器的方法
			wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
		}

		return wrappedBean;
	}

2.2. postProcessAfterInitialization

BeanPostProcessor.postProcessAfterInitialization的调用都被封装到 AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization()方法中。

applyBeanPostProcessorsAfterInitialization()方法的调用在下面三个方法中调用:

  • AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation
  • AbstractAutowireCapableBeanFactory#initializeBean(String, Object, RootBeanDefinition)
  • AbstractAutowireCapableBeanFactory#postProcessObjectFromFactoryBean

2.2.1 resolveBeforeInstantiation

resolveBeforeInstantiation方法的调用有如下三处:

  • AbstractAutowireCapableBeanFactory#createBean(String, RootBeanDefinition, Object[])这里的调用实际上就是上面讲的InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation()的调用后续,所以这里不再重复

  • AbstractAutowireCapableBeanFactory#getSingletonFactoryBeanForTypeCheck()

  • AbstractAutowireCapableBeanFactory#getNonSingletonFactoryBeanForTypeCheck()

2.2.2 initializeBean

上面介绍 BeanPostProcessor.postProcessBeforeInitialization的时候已经说了,所以这里不再赘述

2.2.3 postProcessObjectFromFactoryBean

FactoryBeanRegistrySupport#getObjectFromFactoryBean中调用,在从FactoryBean中获取bean时调用,在此调用可以替换掉FactoryBean中的返回的bean。

3. SmartInstantiationAwareBeanPostProcessor

SmartInstantiationAwareBeanPostProcessorInstantiationAwareBeanPostProcessor之上又扩展了三个方法,不过我们一般不会使用,所以这里简单叙述一下SmartInstantiationAwareBeanPostProcessor三个方法的作用

  • Class<?> predictBeanType(Class<?> beanClass, String beanName) : 在进行bean类型匹配时调用,返回值和期望值进行匹配。
  • Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) : 在Spring加载bean的时候,判断是否需要通过构造注入时,如果返回的值不为空,则进行有参构造注入。

AbstractAutowireCapableBeanFactory#createBeanInstance()

		// determineConstructorsFromBeanPostProcessors 中调用了  determineCandidateConstructors 方法,如果ctors != null, 则进行有参构造autowireConstructor
		Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
		if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
			return autowireConstructor(beanName, mbd, ctors, args);
		}

Object getEarlyBeanReference(Object bean, String beanName): 在 Spring 解决循环依赖时候使用,返回的值将作为ObjectFactory的保存的Object,用于循环依赖的提前暴露。

AbstractAutowireCapableBeanFactory#doCreateBean()

		if (earlySingletonExposure) {
			if (logger.isTraceEnabled()) {
				logger.trace("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			// getEarlyBeanReference 的返回值作为 ObjectFactory 的返回值保存到singletonFactories缓存中
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

在这里插入图片描述
以上:内容部分参考
《Spring源码深度解析》
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++11多线程】线程同步之线程通信:condition_variable

文章目录1.condition_variable2.notify_one()和notify_all()3.wait()3.1 没有第二个参数&#xff1a;while wait()3.2 有第二个参数&#xff1a;wait() lambda需要注意的是&#xff0c;条件变量condition_variable要和互斥锁mutex搭配起来使用。 1.condition_variable cond…

Windows远程连接Redis(Linux)

Windows远程连接Redis&#xff08;Linux&#xff09; 文章目录Windows远程连接Redis&#xff08;Linux&#xff09;1、写在前面2、配置redis.conf3、启动Redis3.1 开启redis服务3.2 启动客户端3.3 Redis命令3.4 查看Redis密码4、关闭Redis5、Java操作Redis1、写在前面 Windows…

安科瑞ARTU100系列模块化远程控制终端单元 开关量输入输出采集模块

安科瑞 王晶淼/刘芳 1.概述 ARTU系列远程终端单元是高性能配电智能化元件&#xff0c;应用于智能配电、工业自动化等领域。ARTU100系列远程终端单元提供开关量输入、开关量输出、模拟量输入、模拟量输出&#xff0c;能够将采集到的信号通过RS485串口、RJ45以太网接口、2G、Lo…

进制+异或

一.调用API String sInteger.toString(十进制&#xff0c;转为0-35进制)&#xff1b; //R进制的字符串转为10进制数 int aInteger.parseInt(s,R); //把R进制的字符串封装成大数类 BigInteger bnew BigInteger(s,R); 二.求R进制下的数位和 2992这个数十进制和为22&#xff0c;…

【计算机视觉】图像分割中FCN、DeepLab、SegNet、U-Net、Mask R-CNN等算法的讲解(图文解释 超详细)

觉得有帮助请点赞关注收藏~~~ 一、FCN分割算法 全卷积神经网络目标分割算法能够端到端的得到每个像素的目标分类结果&#xff0c;与传统的卷积神经网络只能输入固定大小图像和在网络的末端使用几个全连接层得到固定长度的特征向量不同&#xff0c;全卷积神经网络能够接受任意大…

HBase的数据模型和存储原理

HBase的数据模型 HBase中表的逻辑结构 Name Space&#xff08;命名空间&#xff09; 类似于关系型数据库的 DatabBase 概念&#xff0c;每个命名空间下有多个表。HBase有两个自带的命名空间&#xff0c;分别是 hbase 和 default&#xff0c;hbase 中存放的是 HBase 内置的表&a…

向 Excel 和 PowerPoint 添加可编辑地图的指南

本指南规定: 如何将 shapefile 转换为 svg 文件(如果后者不存在) 如何使用 Excel 重命名 svg 文件中的对象以使对象具有正确的地理名称,以及如何将这些添加为 PowerPoint 中的可编辑地图 这样做的目的是为那些没有或不知道该软件的人提供 QGIS 制图的替代方案。通过提供某个…

如何在 Linux 的 shell 里针对特定用户/组来限制某些命令的使用

0-前言 最近&#xff0c;业务侧有个需求&#xff0c;需要禁止特定用户访问linux特定的命令&#xff0c;如禁止用户A使用rm命令。 我们知道&#xff0c;在linux系统中&#xff0c;一切皆文件。 那么&#xff0c;这个问题也可以泛化为&#xff1a; 如何在linux里限制特定用户…

视频理解论文串讲(上)【论文精读】

文章目录1. DeepVedio【CNN 首次 for 视频理解】2. Two-Stream【双流网络】针对上面不同方向&#xff0c;有不同的代表工作LSTM late fusion 方向【Beyond-short-snippets】Early fusion方向【Convolutional fusion】长视频 【TSN temporal segment 分段的思想&#xff0c;good…

5分钟搞定Linux top命令的基本用法

在linux终端中&#xff0c;输入top, 按下Enter&#xff0c;立即进入top界面 如果你操作正确&#xff0c;应该会看到了下面这样的界面。 此情此景&#xff0c;怎么样是不是看懵逼了 不要慌&#xff0c;坐下来耐心听我逼逼赖赖你就会明白这一堆内容是干嘛的 下面介绍每一项分别是…

2022.12.8 半导体器件物理

作业 用迁移电流来推导出公式 电流是I&#xff0c;J是电流密度&#xff0c;W就是横截面 T是这个薄膜的厚度&#xff08;我们最后可以消除掉&#xff09; X就是空间位置&#xff0c;0就是在电极的一端 为什么要减去 因为只有大于阈值电压&#xff0c;才会有水流通过&#xff…

如此简单的时间复杂度计算方法:大O渐进法,你确定不进来康康

对于时间复杂度&#xff0c;空间复杂度&#xff0c;想必这个是大家在学习数据结构的初级阶段就会第一步认识的吧&#xff01;&#xff01;但是&#xff0c;对于复杂度的计算&#xff0c;涉及到了大O渐进法&#xff0c;这个方法是一个笼统的概念&#xff0c;所求得的结果&#x…

大一作业HTML个人网页作业(宠物狗)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

超算对我们的生活有着什么样的影响?

为什么各国都去抢超算的世界第一&#xff1f;因为它对人们的生活有着很大的影响。 一、核武器的成功研发离不开超算 对美国而言&#xff0c;超算的作用首先在研制核武器领域。加利福尼亚的利弗莫尔国家实验室、新墨西哥州的洛斯阿拉莫斯国家实验室、圣地亚国家实验室、橡树岭…

【分立元件】案例:一小批量生产就发现蜂鸣器一致性差?

在文章&#xff1a; 【分立元件】有源蜂鸣器和无源蜂鸣器有什么区别&#xff1f;_阳光宅男李光熠的博客-CSDN博客_有源和无源蜂鸣器 我们讲到有源蜂鸣器内部带震荡源&#xff0c;发声频率固定。无源内部不带震荡源&#xff0c;通过方波去驱动&#xff0c;发音频率可改变。一般…

(Java)SpringMVC学习笔记(一)

前言 今天开始学习SpringMVC&#xff0c;还是跟着尚硅谷视频自学&#xff0c;从两方面把握学习效果&#xff0c;一是知识点理解程度&#xff0c;一是实践程度&#xff0c;而我会将实践具体实现写以成文&#xff0c;用以回顾与分享 JavaWeb知识点简单回顾 JavaBean技术 为了…

订单服务------技术点及亮点

大技术 线程池来实现异步任务&#xff08;亮点1&#xff09; /*** 去结算确认页时封装订单确认页返回需要用的数据* return* throws ExecutionException* throws InterruptedException*/Overridepublic OrderConfirmVo confirmOrder() throws ExecutionException, Interrupted…

小红书推广笔记怎么合作?这里都给大家梳理好啦

截止 2021 年 11 月&#xff0c;小红书月活已达到 2 亿。其中72% 是 90 后群体&#xff0c;50% 的用户在一二线城市。 这些用户流量有着高消费、爱时尚、爱分享、追求品质生活的特点&#xff0c;所以小红书逐渐成为众多品牌方种草推广的必争之地。 小红书推广笔记怎么合作的呢…

FPGA学习笔记(十)IP核之PLL锁相环的学习总结

系列文章目录 一、FPGA学习笔记&#xff08;一&#xff09;入门背景、软件及时钟约束 二、FPGA学习笔记&#xff08;二&#xff09;Verilog语法初步学习(语法篇1) 三、FPGA学习笔记&#xff08;三&#xff09; 流水灯入门FPGA设计流程 四、FPGA学习笔记&#xff08;四&…

pytest自动化测试框架详解+mark标记+fixture夹具

介绍 pytest是python的一种单元测试框架&#xff0c;同自带unittest框架类似&#xff0c;功能&#xff0c;效率更强大 特点&#xff1a; 1. 非常容易上手&#xff0c;入门简单&#xff0c;丰富的文档 2. 支持参数化 3. 执行测试用例的过程中&#xff0c;跳过某些用例&#x…