【Spring Boot 源码学习】OnBeanCondition 详解

news2024/11/26 12:51:01

Spring Boot 源码学习系列

在这里插入图片描述

OnBeanCondition 详解

  • 引言
  • 往期内容
  • 主要内容
    • 1. getOutcomes 方法
    • 2. getMatchOutcome 方法
      • 2.1 ConditionalOnBean 注解处理
      • 2.2 ConditionalOnSingleCandidate 注解处理
      • 2.3 ConditionalOnMissingBean 注解处理
    • 3. getMatchingBeans 方法
  • 总结

引言

上篇博文带大家从 Spring Boot 源码深入详解了 OnClassCondition,那本篇也同样从源码入手,带大家深入了解 OnBeanCondition 的过滤匹配实现。

往期内容

在开始本篇的内容介绍之前,我们先来看看往期的系列文章【有需要的朋友,欢迎关注系列专栏】:

Spring Boot 源码学习
Spring Boot 项目介绍
Spring Boot 核心运行原理介绍
【Spring Boot 源码学习】@EnableAutoConfiguration 注解
【Spring Boot 源码学习】@SpringBootApplication 注解
【Spring Boot 源码学习】走近 AutoConfigurationImportSelector
【Spring Boot 源码学习】自动装配流程源码解析(上)
【Spring Boot 源码学习】自动装配流程源码解析(下)
【Spring Boot 源码学习】深入 FilteringSpringBootCondition
【Spring Boot 源码学习】OnClassCondition 详解

主要内容

话不多说,马上进入正题,我们开始本篇的内容,重点详解 OnBeanCondition 的实现。

在这里插入图片描述

1. getOutcomes 方法

OnBeanCondition 同样也是 FilteringSpringBootCondition 的子类,我们依旧是从 getOutcomes 方法源码来分析【Spring Boot 2.7.9】:

@Order(Ordered.LOWEST_PRECEDENCE)
class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {

	// ...

	@Override
	protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
			AutoConfigurationMetadata autoConfigurationMetadata) {
		ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
		for (int i = 0; i < outcomes.length; i++) {
			String autoConfigurationClass = autoConfigurationClasses[i];
			if (autoConfigurationClass != null) {
				Set<String> onBeanTypes = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnBean");
				outcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class);
				if (outcomes[i] == null) {
					Set<String> onSingleCandidateTypes = autoConfigurationMetadata.getSet(autoConfigurationClass,
							"ConditionalOnSingleCandidate");
					outcomes[i] = getOutcome(onSingleCandidateTypes, ConditionalOnSingleCandidate.class);
				}
			}
		}
		return outcomes;
	}
	// ...
}

上述 getOutcomes 方法中针对 自动配置数据的循环处理逻辑,大致可总结为如下两种:

  • 通过调用 AutoConfigurationMetadata 接口的 get(String className, String key) 方法来获取与autoConfigurationClass 关联的名为 "ConditionalOnBean" 的条件属性值,可能含多个,存入 Set 集合 onBeanTypes 变量中;接着调用 getOutcome(Set<String> requiredBeanTypes, Class<? extends Annotation> annotation) 方法来获取过滤匹配结果,并赋值给 outcomes[i]

    我们以 RedisCacheConfiguration 为例,可以看到如下配置:
    在这里插入图片描述

  • 如果上述过滤匹配结果 outcomes[i]null,则通过调用 AutoConfigurationMetadata 接口的 get(String className, String key) 方法来获取与autoConfigurationClass 关联的名为 "ConditionalOnSingleCandidate" 的条件属性值,可能含多个,存入 Set 集合 onSingleCandidateTypes 变量中;接着调用 getOutcome(Set<String> requiredBeanTypes, Class<? extends Annotation> annotation) 方法来获取过滤匹配结果,并赋值给 outcomes[i]

    我们以 MongoDatabaseFactoryConfiguration 为例,可以看到如下配置:
    在这里插入图片描述

有关 AutoConfigurationMetadata 接口的 get(String className, String key) 方法的逻辑,请查看 Huazie 的 上一篇博文【Spring Boot 源码学习】OnClassCondition 详解,这里不再赘述。

下面我们继续查看 getOutcome(Set<String> requiredBeanTypes, Class<? extends Annotation> annotation) 方法的逻辑:

private ConditionOutcome getOutcome(Set<String> requiredBeanTypes, Class<? extends Annotation> annotation) {
	List<String> missing = filter(requiredBeanTypes, ClassNameFilter.MISSING, getBeanClassLoader());
	if (!missing.isEmpty()) {
		ConditionMessage message = ConditionMessage.forCondition(annotation)
			.didNotFind("required type", "required types")
			.items(Style.QUOTE, missing);
		return ConditionOutcome.noMatch(message);
	}
	return null;
}

进入 getOutcome 方法,可以看到:

  • 首先调用父类 FilteringSpringBootCondition 中的 filter 方法,来获取给定的类集合 requiredBeanTypes 中加载失败的类集合 missing【即当前类加载器中不存在的类集合】;
  • 如果 missing 不为空,说明存在加载失败的类,则返回 不满足过滤匹配的结果【即 ConditionOutcome.noMatch,其中没有找到 missing 中需要的类型】;
  • 如果 missing 为空,直接返回 null 即可。

2. getMatchOutcome 方法

OnClassCondition 一样,OnBeanCondition 同样实现了 FilteringSpringBootCondition 的父类 SpringBootCondition 中的抽象方法 getMatchOutcome 方法。

有关 SpringBootCondition 的介绍,这里不赘述了,请查看笔者的 【Spring Boot 源码学习】OnClassCondition 详解。

通过查看 getMatchOutcome 方法源码,可以看到针对 ConditionalOnBean 注解、ConditionalOnSingleCandidate 注解 和 ConditionalOnMissingBean 注解的三块处理逻辑,下面来一一讲解:

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
	ConditionMessage matchMessage = ConditionMessage.empty();
	MergedAnnotations annotations = metadata.getAnnotations();
	// ConditionalOnBean 注解处理
	// ConditionalOnSingleCandidate 注解处理
	// ConditionalOnMissingBean 注解处理
	return ConditionOutcome.match(matchMessage);
}

2.1 ConditionalOnBean 注解处理

我们来看看 ConditionalOnBean 注解处理逻辑的源码:

	if (annotations.isPresent(ConditionalOnBean.class)) {
		Spec<ConditionalOnBean> spec = new Spec<>(context, metadata, annotations, ConditionalOnBean.class);
		MatchResult matchResult = getMatchingBeans(context, spec);
		if (!matchResult.isAllMatched()) {
			String reason = createOnBeanNoMatchReason(matchResult);
			return ConditionOutcome.noMatch(spec.message().because(reason));
		}
		matchMessage = spec.message(matchMessage)
			.found("bean", "beans")
			.items(Style.QUOTE, matchResult.getNamesOfAllMatches());
	}

针对上述代码,且听分析如下:

  • 首先调用 MergedAnnotations 接口的 isPresent(Class<A> annotationType) 方法判断指定的注解类型是直接存在或者元存在【这里相当于调用 get(annotationType).isPresent()】,如果返回 true,表示存在指定的注解类型。
  • 如果存在 @ConditionalOnBean,则
    • 创建一个条件规范 Spec 对象,该类是从底层的注解中提取的搜索规范;
    • 接着,调用 getMatchingBeans 方法,并从上下文【context】中获取与条件规范【spec】匹配的 Spring Beans 的结果【MatchResult】;
    • 然后,检查匹配结果,如果不是所有的条件都匹配,则继续如下:
      • 调用 createOnBeanNoMatchReason 方法,创建一个描述条件不匹配原因的字符串并返回;
      • 返回一个表示未匹配条件的 ConditionOutcome 对象【其中包含了条件规范的消息以及不匹配的原因】;
    • 否则,更新匹配消息,并记录 找到了所有匹配的 Spring Beans

2.2 ConditionalOnSingleCandidate 注解处理

我们继续查看 ConditionalOnSingleCandidate 注解处理逻辑的源码:

	if (metadata.isAnnotated(ConditionalOnSingleCandidate.class.getName())) {
		Spec<ConditionalOnSingleCandidate> spec = new SingleCandidateSpec(context, metadata, annotations);
		MatchResult matchResult = getMatchingBeans(context, spec);
		if (!matchResult.isAllMatched()) {
			return ConditionOutcome.noMatch(spec.message().didNotFind("any beans").atAll());
		}
		Set<String> allBeans = matchResult.getNamesOfAllMatches();
		if (allBeans.size() == 1) {
			matchMessage = spec.message(matchMessage).found("a single bean").items(Style.QUOTE, allBeans);
		}
		else {
			List<String> primaryBeans = getPrimaryBeans(context.getBeanFactory(), allBeans,
					spec.getStrategy() == SearchStrategy.ALL);
			if (primaryBeans.isEmpty()) {
				return ConditionOutcome
					.noMatch(spec.message().didNotFind("a primary bean from beans").items(Style.QUOTE, allBeans));
			}
			if (primaryBeans.size() > 1) {
				return ConditionOutcome
					.noMatch(spec.message().found("multiple primary beans").items(Style.QUOTE, primaryBeans));
			}
			matchMessage = spec.message(matchMessage)
				.found("a single primary bean '" + primaryBeans.get(0) + "' from beans")
				.items(Style.QUOTE, allBeans);
		}
	}

同样针对上述代码,跟着 Huazie 来一步步分析下:

  • 首先调用 AnnotatedTypeMetadata 接口的 isAnnotated(String annotationName) 方法判断元数据中是否存在指定注解。如果返回 true,表示元数据中存在指定注解。
  • 如果元数据中存在 @ConditionalOnSingleCandidate 注解,则
    • 创建了一个 SingleCandidateSpec 的对象 spec ,并传入上下文 【context】、元数据 【metadata】 和注解信息 【annotations】 ,该类是专门针对 @ConditionalOnSingleCandidate 注解的条件规范。
    • 接着调用 getMatchingBeans 方法对 context 中的所有 bean 进行匹配,并将与条件规范【spec】匹配的 Spring Beans 的结果存储在 matchResult 变量中;
    • 如果没有匹配的 bean,则返回表示未匹配条件的 ConditionOutcome 对象【其中记录了 没有找到任何 bean 的信息】;
    • 否则,获取匹配的所有 bean 名称并存储在 allBeans 变量中。
      • 如果仅有一个匹配的 bean,则更新匹配消息,并记录找到了 单个 bean 的信息;
      • 否则,获取首选 bean 名称列表,并检查列表是否为空;
        • 如果列表为空,则返回表示未匹配条件的 ConditionOutcome 对象【其中记录了 一个首选 bean 也没有找到 的信息】;
        • 如果首选 bean 名称列表包含多个 bean,则返回表示未匹配条件的 ConditionOutcome 对象【其中记录了 找到了多个首选 bean 的信息】;
        • 否则,更新匹配消息,并记录 找到了首选 bean 的信息。

2.3 ConditionalOnMissingBean 注解处理

我们继续查看 ConditionalOnMissingBean 注解处理逻辑的源码:

	if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
		Spec<ConditionalOnMissingBean> spec = new Spec<>(context, metadata, annotations,
				ConditionalOnMissingBean.class);
		MatchResult matchResult = getMatchingBeans(context, spec);
		if (matchResult.isAnyMatched()) {
			String reason = createOnMissingBeanNoMatchReason(matchResult);
			return ConditionOutcome.noMatch(spec.message().because(reason));
		}
		matchMessage = spec.message(matchMessage).didNotFind("any beans").atAll();
	}

经过上述两种处理逻辑的分析,相信大家应该可以看懂第三种处理逻辑的分析:

  • 首先调用 AnnotatedTypeMetadata 接口的 isAnnotated(String annotationName) 方法判断元数据中是否存在指定注解。如果返回 true,表示元数据中存在指定注解。
  • 如果存在 @ConditionalOnMissingBean 注解,则
    • 创建一个条件规范 Spec 对象,该类是从底层的注解中提取的搜索规范;
    • 接着,调用 getMatchingBeans 方法,并从上下文【context】中获取与条件规范【spec】匹配的 Spring Beans 的结果【MatchResult】;
    • 如果存在任何一个匹配的 bean,则
      • 调用 createOnMissingBeanNoMatchReason 方法,创建一个描述条件不匹配原因的字符串并返回;
      • 返回一个表示未匹配条件的 ConditionOutcome 对象【其中包含了条件规范的消息以及不匹配的原因】;
    • 否则,更新匹配消息,并记录 找不到指定类型的 bean 的信息。

3. getMatchingBeans 方法

上述三种注解处理逻辑中,我们都看到了调用 getMatchingBeans 方法,下面重点来讲解一下:

protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> spec) {
	// ...
}

我们可以看到 getMatchingBeans 方法,有两个参数,它们分别是 上下文 【context】和 条件规范【spec】;

继续看 getMatchingBeans 方法内部逻辑:

	ClassLoader classLoader = context.getClassLoader();
	ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();

这里从上下文【context】中获取 ClassLoaderConfigurableListableBeanFactory

知识拓展:

  • ClassLoaderJava 中的一个接口,用于加载类。它是 Java 类加载机制的核心部分,负责将 .class 文件转换为 Java 类实例。ClassLoader 可以从不同的来源(如文件系统、网络、数据库等)加载类,也可以实现自定义的类加载逻辑。
  • ConfigurableListableBeanFactorySpring 框架中的一个核心接口,它扩展了ListableBeanFactory 接口,提供了更多的配置和扩展功能。它是一个 bean 工厂的抽象概念,用于管理 Spring 容器中的 bean 对象。ConfigurableListableBeanFactory 提供了添加、移除、注册和查找 bean 的方法,以及设置和获取 bean 属性值的功能。它还支持bean 的后处理和事件传播。
	boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;

这里根据 Spec 对象的 SearchStrategy 属性来确定是否考虑 bean 的层次结构。如果SearchStrategyCURRENT【】,则不考虑层次结构【即 considerHierarchy 为 false】;否则,考虑层次结构【即 considerHierarchy 为 true】。

	Set<Class<?>> parameterizedContainers = spec.getParameterizedContainers();

这里获取 Spec 对象的 parameterizedContainers 属性,这是一个包含参数化容器类型的集合

	if (spec.getStrategy() == SearchStrategy.ANCESTORS) {
		BeanFactory parent = beanFactory.getParentBeanFactory();
		Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,
				"Unable to use SearchStrategy.ANCESTORS");
		beanFactory = (ConfigurableListableBeanFactory) parent;
	}

如果 Spec 对象的 SearchStrategy 属性是 SearchStrategy.ANCESTORS,则调用 getParentBeanFactory 方法获取其父工厂,并将其转换为 ConfigurableListableBeanFactory 类型。

	MatchResult result = new MatchResult();

新建一个 MatchResult 对象,用于存储匹配结果;

	Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy,
				spec.getIgnoredTypes(), parameterizedContainers);

调用 getNamesOfBeansIgnoredByType 方法,获取被忽略类型的 bean 名称集合 beansIgnoredByType

	for (String type : spec.getTypes()) {
		Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type,
				parameterizedContainers);
		Iterator<String> iterator = typeMatches.iterator();
		while (iterator.hasNext()) {
			String match = iterator.next();
			if (beansIgnoredByType.contains(match) || ScopedProxyUtils.isScopedTarget(match)) {
				iterator.remove();
			}
		}
		if (typeMatches.isEmpty()) {
			result.recordUnmatchedType(type);
		}
		else {
			result.recordMatchedType(type, typeMatches);
		}
	}

遍历 Spec 对象的 types 属性,它是一个 Set<String> 集合

  • 首先,针对每个类型 type,调用 getBeanNamesForType 方法获取匹配的 bean 名称集合 typeMatches
  • 然后,使用迭代器遍历这个集合,如果集合中的某个元素在被忽略类型的集合中,就将其从迭代器中移除。
  • 最后,如果 typeMatches 集合为空,则记录未匹配的类型;否则,记录匹配的类型。
	for (String annotation : spec.getAnnotations()) {
		Set<String> annotationMatches = getBeanNamesForAnnotation(classLoader, beanFactory, annotation,
				considerHierarchy);
		annotationMatches.removeAll(beansIgnoredByType);
		if (annotationMatches.isEmpty()) {
			result.recordUnmatchedAnnotation(annotation);
		}
		else {
			result.recordMatchedAnnotation(annotation, annotationMatches);
		}
	}

遍历 Spec 对象的 annotations 属性:

  • 首先,针对每个注解 annotation,调用 getBeanNamesForAnnotation 方法获取匹配的 bean 名称集合 annotationMatches
  • 然后,从 annotationMatches 集合中移除被忽略类型的集合。
  • 最后,如果 annotationMatches 集合为空,则记录未匹配的注解;否则,记录匹配的注解。
	for (String beanName : spec.getNames()) {
		if (!beansIgnoredByType.contains(beanName) && containsBean(beanFactory, beanName, considerHierarchy)) {
			result.recordMatchedName(beanName);
		}
		else {
			result.recordUnmatchedName(beanName);
		}
	}

遍历 Spec 对象的 names 属性,对于每个 bean 名称,如果它不在被忽略类型的集合中,并且它在 bean 工厂中存在,就记录匹配的名称;否则,记录未匹配的名称。

总结

本篇 Huazie 带大家介绍了自动配置过滤匹配子类 OnBeanCondition ,内容较多,感谢大家的支持;笔者接下来的博文还将详解 OnWebApplicationCondition 的实现,敬请期待!!!

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

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

相关文章

MQ - 15 集群篇_如何构建分布式的消息队列集群(下)

文章目录 导图Pre概述元数据存储服务设计选型基于第三方存储引擎集群内部自实现元数据存储ZooKeeper 的集群构建Kafka 的集群构建基于 ZooKeeper 的集群基于 KRaft 的集群缩容的处理方式总结导图 Pre 接着 MQ - 14 集群篇_如何构建分布式的消息队列集群(上)</

唯品会获得vip商品详情 API 返回值说明(唯品会商品价格销量详情图主图)

item_get-获得vip商品详情 vip.item_get 请求地址 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff09;[item_search,item_get,item…

threejs给3d模型中的物体换肤(修改材质)

变成这样 this.otherModel.traverse(function (child) {if (child instanceof THREE.Mesh && child.name Cylinder240) {// 导入纹理const textureLoader new THREE.TextureLoader();const floorColortextureLoader.load(require(../../../public/img/color.jpg));co…

在Linux中安装nginx-1.20.1+php-7.4.28(增加扩展)

NginxPHP安装在公网IP为x.x.x.x的服务器上 需要下载安装的软件版本&#xff1a;nginx-1.20.1php-7.4.28 需要增加的PHP扩展如下&#xff1a; 在编译安装php-7.4.28时加上的pcntl&#xff1b; 单独下载安装的Wxwork_finance_sdk&#xff1b;&#xff08;在编译安装php-7.4.2…

django和celery的项目,nginx和uwsgi协议,在通过api端口进行deeplearning任务的训练和排队

问题汇总 redis 安装django和celery的安装nginx和uwsgi的安装 一. Django 的项目&#xff0c;有个runserver直接起了一个webserver&#xff0c;为什么还要Nginx包一层&#xff0c;起一个webserver呢&#xff1f; Nginx的性能比Django自带的Webserver的性能要好&#xff0c;pyt…

Jenkins用户管理(二):不同用户分配不同的任务访问权限

需求:不同用户访问到不同的Jenkins任务。 依赖插件:Role-based Authorization Strategy 1. 插件安装 进入【系统管理】-【插件管理】-【可用插件】,搜索Role-based Authorization Strategy进行安装,随后重启jenkins 2. 全局安全配置 进入【系统管理】-【全局安全配置】,【…

引用js文件实现汉字转拼音

先看效果 具体需求是 第一个文本框输入汉字&#xff0c;第三个显示拼音&#xff0c; 先引用js <script src"/js/pinyinutil.js"></script> html这样 <el-input v-model"inputText" style"width:220px" placeholder"请输入…

2023智慧云打印小程序源码多店铺开源版 +前端

智慧自助云打印系统/智慧云打印小程序源码 前端 这是一款全新的基于Thinkphp的最新自助打印系统&#xff0c;最新UI界面设计的云打印小程序源码

前端开发必备:icon封装技巧

main.js import svgIcon from /components/SvgIcon Vue.component(svg-icon-full, svgIcon) const requireAll requireContext > requireContext.keys().map(requireContext) const req require.context(/assets/icons/svg, false, /\.svg$/) // icon位置 requireAll(re…

【运维 Pro】时序场景实践与原理 - 2. 宽表,窄表与 JSON 字段

【运维Pro】: 由 YMatrix 售前和售后团队负责的栏目。除了介绍日常的数据库运维和使用知识&#xff0c;我们更希望能够通过介绍这些知识背后的原理&#xff0c;让大家和我们一起感知数据库的美妙。 摘要 在上一期 《时序场景实践与原理 - 1.分布与分区》中&#xff0c;我们围…

探索智能应用的基石:多模态大模型赋能文档图像处理

目录 0 写在前面1 文档图像分析新重点2 token荒&#xff1a;电子文档助力大模型3 大模型赋能智能文档分析4 文档图像大模型应用可能性4.1 专有大模型4.2 多模态模型4.3 设计思路 总结 0 写在前面 中国智能产业高峰论坛(CIIS2023)旨在为政企研学各界学者专家提供同台交流的机会…

Denoising Diffusion Autoencoders are Unified Self-supervised Learners

Denoising Diffusion Autoencoders are Unified Self-supervised Learners (Paper reading) Weilai Xiang, Beihang University, arXiv23, Code, Paper 1. 前言 受最近扩散模型进展的启发&#xff0c;这让人想起去噪自编码器&#xff0c;我们研究了它们是否可以通过生成预训…

1952-2018年中国各省份人均GDP数据(消涨处理)

1952-2018年中国各省份人均GDP数据&#xff08;消涨处理&#xff09; 1、时间&#xff1a;1952-2018年 2、范围&#xff1a;30省市 3、指标&#xff1a;人均GDP 4、来源&#xff1a;《新中国60周年统计汇编》和各省年鉴 5、指标解释&#xff1a; 过程为环比人均GDP指数转…

【Vue】使用vue-cli搭建SPA项目的路由,嵌套路由

一、SPA项目的构建 1、前期准备 我们的前期的准备是搭建好Node.js,测试&#xff1a; node -v npm -v2、利用Vue-cli来构建spa项目 2.1、什么是Vue-cli Vue CLI 是一个基于 Vue.js 的官方脚手架工具&#xff0c;用于自动生成vue.jswebpack的项目模板&#xff0c;它可以帮助开发者…

openGauss学习笔记-73 openGauss 数据库管理-创建和管理索引

文章目录 openGauss学习笔记-73 openGauss 数据库管理-创建和管理索引73.1 背景信息73.2 操作步骤73.2.1 创建索引73.2.2 修改索引分区的表空间73.2.3 重命名索引分区73.2.4 查询索引73.2.5 删除索引73.2.6 创建索引的方式73.2.6.1 创建普通索引73.2.6.2 创建多字段索引73.2.6.…

升级iOS17后可以降级吗?iOS17退回iOS16方法教程分享

iOS 17已上线几天&#xff0c;从网上用户的反馈和媒体机构的报告来看&#xff0c;iOS17系统对旧机型来说并不友好&#xff0c;除了电池续航下降以外&#xff0c;占用大量储存空间&#xff0c;BUG也不少。 苹果于 9 月 7 日发布了 iOS 16.6.1 版本&#xff0c;如果升级iOS17后发…

opencv实现仿射变换

什么是仿射变换&#xff1f; 代码实现 import numpy as np import cv2 as cv import matplotlib.pyplot as plt#设置字体 from pylab import mpl mpl.rcParams[font.sans-serif] [SimHei]#图像的读取 img cv.imread("lena.png")#仿射变换 rows , cols img.shape[…

计算机视觉与深度学习-全连接神经网络-训练过程-模型正则与超参数调优- [北邮鲁鹏]

目录标题 神经网络中的超参数学习率超参数优化方法网格搜索法随机搜索法 超参数搜索策略粗搜索精搜索 超参数的标尺空间 神经网络中的超参数 超参数 网络结构&#xff1a;隐层神经元个数&#xff0c;网络层数&#xff0c;非线性单元选择等优化相关&#xff1a;学习率、dorpou…

期权如何交易?期权如何做模拟交易?

买卖期权的第一步就是要有期权账户&#xff0c;国内的期权品种有商品期权和ETF期权以及股指期权&#xff0c;每种的开户方式和要求都不同&#xff0c;下文为大家介绍期权如何交易&#xff1f;期权如何做模拟交易&#xff1f; 一、期权交易需要开立一个期权账户&#xff0c;可以…

OpenCV图像金字塔

什么是图像金字塔&#xff1f; 向上采样 &#xff1a;cv.pyrUp(img) 向下采样 : cv.pyrDown(img) 代码实现 import numpy as np import cv2 as cv import matplotlib.pyplot as plt#图像的读取 img cv.imread("lena.png")#进行图像采样 up_img cv.pyrUp(img) #上…