请你谈谈:BeanDefinition类作为Spring Bean的建模对象,与BeanFactoryPostProcessor之间的羁绊

那么,我们如何理解Spring Bean的建模对象呢?简而言之,它是指用于描述和配置Bean实例化过程的模型对象。有人可能会提出疑问,既然只需要Class(类)就可以实例化一个对象,Class作为类的元数据,可以视作对象的建模对象,为何Spring还需要其他机制来建立Bean呢?这其中的关键在于,Class本身虽然包含了类的定义和实例化的基础信息,但它无法涵盖Spring框架中Bean的所有特性和配置。例如,Bean的作用域(Scope)、注入模型(Injection Model)、是否采用懒加载(Lazy Initialization)等属性,这些都是在Spring容器级别进行配置和管理的,而这些信息无法通过简单的Class元数据来表达。

因此,为了更全面地描述和管理Bean,Spring引入了一个名为BeanDefinition的类。BeanDefinition类作为Spring Bean的建模对象,能够抽象出Bean的各种属性和配置信息,包括但不限于作用域、生命周期回调、初始化方法、销毁方法、依赖注入等。通过BeanDefinition,Spring能够精确地控制Bean的实例化过程,确保Bean能够按照预期的方式被创建、配置和管理。
在这里插入图片描述
针对上述图示的详细文字说明如下:在一个给定的场景中,我们假设磁盘上存有N个.java源文件。首先,我们需要通过Java编译器将这些源文件逐一编译成相应的.class字节码文件。接着,当Java虚拟机(JVM)启动时,它会按照特定的类加载机制,将这些.class文件从磁盘加载到JVM的内部内存中。一旦.class文件被成功加载到内存中,JVM便能根据这些文件中定义的类模板信息来执行后续的操作。当JVM的字节码执行器在执行过程中遇到new关键字时,它会根据该关键字所指向的类的模板信息,在JVM的堆内存中为该类实例化一个对象,并为其分配相应的内存空间。这一过程确保了对象在JVM中的正确创建和初始化。

但是spring的bean实例化过程和一个普通java对象的实例化过程还是有区别的。
在这里插入图片描述
前提:假设在你的项目或者磁盘上有X和Y两个类,X是被加了spring注解的,Y没有加spring的注解;也就是正常情况下当spring容器启动之后通过getBean(X)能正常返回X的bean,但是如果getBean(Y)则会出异常,因为Y不能被spring容器扫描到不能被正常实例化;

在Spring容器启动的过程中,ConfigurationClassPostProcessor这一Bean工厂的后置处理器会被调用,以完成特定配置的扫描和解析。这里的“扫描”实际上是指Spring从类路径中识别并读取类的元数据。然而,读取到的类的元数据,如类的类型、名称和构造方法等,并不会直接存储在Class对象中。尽管Class对象确实包含了这些基本信息,但Spring在实例化Bean时,还需要考虑更多的配置属性,如作用域(scope)、延迟加载(lazy)、依赖关系(dependsOn)等。

为了满足这些需求,Spring设计了一个名为BeanDefinition的类,用于存储和管理Bean的完整配置信息。因此,当Spring读取到类的元数据后,

①它会为每个类实例化一个BeanDefinition对象,通过该对象的各种set方法将相关信息(包括从Class对象获取的元数据以及额外的配置属性)存储起来。

②在扫描过程中,每当Spring遇到一个符合规则的类,它都会实例化一个新的BeanDefinition对象,并根据类的名称自动生成一个Bean的名称(例如,对于名为IndexService的类,Spring会按照其命名规则生成一个名为indexService的Bean名称)。当然,Spring也提供了灵活的命名策略,允许开发者自定义名称生成器以覆盖默认行为。

③随后,Spring会将这个BeanDefinition对象及其对应的Bean名称存储在一个Map中,其中键(key)为Bean名称,值(value)为BeanDefinition对象。这个过程确保了Bean的元数据和配置信息在Spring容器中的有序管理和高效检索。至此,上述流程中的第①、②、③步便完成了,为后续的Bean创建和依赖注入等操作奠定了基础。

这里需要说明的是spring启动的时候会做很多工作,不仅仅是完成扫描,在扫描之前spring还干了其他大量事情;比如实例化beanFacctory、比如实例化类扫描器等等。

Appconfig.java
@ComponentScan("com.luban.beanDefinition")
@Configuration
public class Appconfig {
}X.java
@Component
public class X {public X(){System.out.println("X Constructor");}
}Y.java
public class Y {
}Test.java
public class Test{public static void main(String[] args) {AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();ac.register(Appconfig.class);ac.refresh();}}

在上述代码示例中,存在两个类:X和Y。其中,X类被标注了某个注解,而Y类则未被标注。在X类中,存在一个构造方法,该方法在X类实例化时会输出"X Constructor"。根据Spring框架的工作原理,当Spring容器启动时,它会执行一系列的初始化步骤,其中包括对带有特定注解的类的扫描和解析。

具体来说,在Spring容器的初始化过程中,会调用org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors方法。这个方法负责执行Bean工厂的后置处理器,其中就包括了对带有特定注解的类的扫描和解析。为了验证这一过程,我在该方法上设置了一个断点。

在Spring框架中,invokeBeanFactoryPostProcessors 是一个非常重要的方法,它负责在Spring容器初始化过程中调用所有实现了 BeanFactoryPostProcessor 接口的bean。这些后处理器允许在bean的定义被加载到Spring的bean工厂(BeanFactory)之后,但在bean被实例化之前,对bean定义进行修改或添加。

当应用程序启动并触发Spring容器初始化时,会调用invokeBeanFactoryPostProcessors方法。在方法执行之前,检查Spring内部的beanDefinitionMap(或类似的存储结构),发现其中并没有键为"x"的元素,这说明此时X类尚未被扫描和解析。

随着invokeBeanFactoryPostProcessors方法的执行,Spring开始扫描并解析带有注解的类。当扫描到X类时,它会将X类的信息解析为一个BeanDefinition对象,并将该对象以键为"x"的形式存储到beanDefinitionMap中。

完成扫描和解析后,我再次检查了beanDefinitionMap,发现其中已经包含了键为"x"的元素,其对应的值正是一个BeanDefinition对象。然而,此时并未发现控制台输出"X Constructor",这说明尽管X类已经被扫描并解析为一个BeanDefinition对象,但X类的实例并未被创建。

这个例子清晰地展示了Spring框架在初始化过程中的一个关键步骤:首先扫描并解析带有注解的类,将其信息存储为BeanDefinition对象并放入beanDefinitionMap中;随后,在适当的时候(如依赖注入时),才会根据这些BeanDefinition对象来实例化相应的类。关于beanDefinitionMap的详细工作原理和用途,将在后续的文章中进一步探讨。在本文中,读者只需理解它是一个专门用于存储BeanDefinition对象的集合即可。

在这里插入图片描述
在这里插入图片描述

④在Spring将类所对应的BeanDefinition对象存储到Map之后,它会进一步调用程序员提供的Bean工厂后置处理器BeanFactoryPostProcessor。那么,什么是BeanFactoryPostProcessor呢?在Spring的架构中,BeanFactoryPostProcessor是一个接口,用于定义对BeanFactory进行额外处理或修改的逻辑。任何实现了该接口的类都可以被视为一个BeanFactoryPostProcessor。关于BeanFactoryPostProcessor接口的详细源码解析,我们将在后续的文章中深入探讨。在此,我们先简要概述其基本作用。

值得注意的是,Spring内部也实现了BeanFactoryPostProcessor接口,例如ConfigurationClassPostProcessor。该类在Spring源码中扮演着举足轻重的角色,它负责执行诸如扫描配置类、解析配置信息等重要功能。在我看来,ConfigurationClassPostProcessor是理解Spring源码过程中不可或缺的关键类之一。由于其功能复杂且广泛,我们将在后续的分析中逐一深入探讨。现在,让我们先来看一下这个类的类结构图,以便对其有一个初步的了解。
在这里插入图片描述
ConfigurationClassPostProcessor作为Spring框架中的一个核心组件,实现了多个接口,其中与本文直接相关的是BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor。尽管BeanDefinitionRegistryPostProcessor继承了BeanFactoryPostProcessor,但为了更好地理解其角色和用途,我们将其视为两个独立的接口。

在Spring的启动过程中,ConfigurationClassPostProcessor发挥了关键作用。具体来说,当Spring执行①②③步骤时,它实际上是调用了ConfigurationClassPostProcessor中实现的BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry方法。这一步骤负责处理与BeanDefinition注册相关的逻辑。

进入第④步时,Spring会执行BeanFactoryPostProcessor接口的postProcessBeanFactory方法。这里需要明确的是,Spring首先会调用ConfigurationClassPostProcessor自身的postProcessBeanFactory方法,这是因为它本身也实现了该接口。之后,如果应用程序开发者提供了自定义的BeanFactoryPostProcessor实现,Spring也会依次调用这些自定义的postProcessBeanFactory方法。

为了更清晰地表达这一过程,我们注意到第④步在图中是以红色虚线表示的,这是因为这一步的执行依赖于开发者是否提供了自定义的BeanFactoryPostProcessor。然而,不论开发者是否提供了自定义实现,Spring都会执行其内置的BeanFactoryPostProcessor,即ConfigurationClassPostProcessor。因此,图表在描述第④步时确实可以进一步细化,以明确表示Spring对内置和自定义BeanFactoryPostProcessor的执行。

综上所述,Spring在启动过程中通过ConfigurationClassPostProcessor的BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor接口实现了对BeanDefinition的注册和Bean工厂的后处理。无论是内置的还是自定义的BeanFactoryPostProcessor,都在Spring的启动流程中扮演着重要角色。

重点:我们用自己的话总结一下BeanFactoryPostProcessor的执行时机(不管内置的还是程序员提供):

BeanFactoryPostProcessor的执行时机是在Spring容器创建并初始化Bean定义之后,但在实例化任何Bean之前。这是一个关键的扩展点,允许开发者在Spring容器完成基本的Bean定义加载和解析之后,对BeanFactory的内容进行额外的处理或修改。

无论是Spring内置的BeanFactoryPostProcessor实现(如ConfigurationClassPostProcessor),还是程序员自己提供的自定义BeanFactoryPostProcessor实现,它们的postProcessBeanFactory方法都会在容器准备实例化Bean之前被调用。

具体来说,当Spring容器启动时,它会首先读取配置文件或注解信息,并将这些配置转化为内部的Bean定义(BeanDefinition)。完成这一步之后,Spring会查找所有实现了BeanFactoryPostProcessor接口的类,并依次调用它们的postProcessBeanFactory方法。这些处理器可以对BeanFactory中的Bean定义进行修改、添加或删除,从而影响最终的Bean实例化过程。

因此,BeanFactoryPostProcessor为开发者提供了一个在Spring容器启动过程中干预Bean定义的机会,使得开发者能够根据自己的需求对容器进行定制和扩展。

那么第④步当中提到的执行程序员提供的BeanFactoryPostProcessor到底有什么意义呢?程序员提供BeanFactoryPostProcessor的场景在哪里?有哪些主流框架这么干过呢?

首先回答第一个问题,意义在哪里?可以看一下这个接口的方法签名:

void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 
throws BeansException;

其实讨论这个方法的意义就是讨论BeanFactoryPostProcessor的作用或者说意义,参考这个方法的一句:

Modify the application context’s internal bean factory after its standard initialization
在应用程序上下文的标准初始化之后修改内部的BeanFactory

在Spring框架中,BeanFactoryPostProcessor(此处特指直接实现此接口的后置处理器,不涉及BeanDefinitionRegistryPostProcessor)被视为一个关键的扩展点。Spring框架通过提供这样的扩展点,旨在赋予开发者对BeanFactory初始化过程进行干预的能力,这是深入学习Spring源码并对其进行二次开发或构建优雅插件的重要一环。需要特别强调的是,这里的核心焦点在于“初始化过程”,而非“实例化过程”。这两者在Spring框架的上下文中具有显著的区别,特别是在阅读Spring源码时,应特别留意这两个术语的使用。

深入Spring的源码,我们会发现整个容器初始化过程实质上是由各种后置处理器(PostProcessor)的调用序列所构成的。这些后置处理器大致可以分为两类:一类关注于bean的实例化过程,而另一类则关注于bean的初始化过程。这种分类并非主观臆想,而是基于Spring框架对初始化和实例化概念的明确区分。熟悉Spring后置处理器体系的开发者,从其命名规则中就能洞察到Spring对这两者之间的严格区分。

严谨地阐述,BeanFactoryPostProcessor的主要作用并不在于干预BeanFactory的实例化过程,即BeanFactory如何被创建(new)出来。然而,一旦BeanFactory实例化完成,BeanFactoryPostProcessor便能够介入其后的属性配置和修改阶段,也就是我们通常所说的“初始化”过程。

具体来说,BeanFactoryPostProcessor接口中定义的方法postProcessBeanFactory接受一个类型为ConfigurableListableBeanFactory的参数,这个参数代表了已经实例化并配置好的BeanFactory对象。由于Spring在调用postProcessBeanFactory方法时传递的是已经准备好的beanFactory实例,因此开发者可以在这个方法中对BeanFactory进行进一步的配置或修改。

这意味着,通过实现BeanFactoryPostProcessor接口并覆盖postProcessBeanFactory方法,开发者可以定制BeanFactory的初始化过程,例如添加、修改或删除bean定义,调整bean的属性等。然而,需要强调的是,在操作过程中应当遵循Spring框架的设计原则和最佳实践,以确保系统的稳定性和可维护性。

在处理BeanFactory对象时,仅仅进行简单的打印(如使用System.out.println)是远远不够的,这种做法并不能充分发挥BeanFactory的功能,更不能称之为“肆意妄为”。实际上,BeanFactory提供了丰富的功能和API供开发者使用,但我们必须先深入理解其特性和API,才能有效地利用它。

尽管BeanFactory本身具有复杂性,不在此展开详细讨论,但与本文内容紧密相关的是beanDefintionMap(存储BeanDefinition的集合),这个重要的数据结构就定义在BeanFactory中。BeanFactory也提供了相应的API供程序员操作这个Map,比如允许我们修改其中已存在的BeanDefinition对象,或者向其中添加新的BeanDefinition对象。这些操作都需要开发者对BeanFactory和BeanDefinition有深入的了解,以确保在操作时遵循最佳实践,并保持系统的稳定性和可维护性。

@Component
public class TestBeanFactoryPostPorcessor implements BeanFactoryPostProcessor {@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {// 转换为子类,因为父类没有添加beanDefintion对象的apiDefaultListableBeanFactory defaultbf =
(DefaultListableBeanFactory) beanFactory;// new一个Y的beanDefinition对象,方便测试动态添加GenericBeanDefinition y= new GenericBeanDefinition();y.setBeanClass(Y.class);// 添加一个beanDefinition对象,原本这个Y没有被spring扫描到defaultbf.registerBeanDefinition("y", y);// 得到一个已经被扫描出来的beanDefintion对象x// 因为X本来就被扫描出来了,所以是直接从map中获取BeanDefinition x = defaultbf.getBeanDefinition("x");// 修改这个X的beanDefintion对象的class为Z// 原本这个x代表的class为X.class;现在为Z.classx.setBeanClassName("com.luban.beanDefinition.Z");}}

在项目中,我们定义了三个类:X、Y和Z。其中,仅有类X被标注了@Component注解。因此,在代码执行到特定方法时,Spring容器仅扫描并识别到了类X,导致BeanFactory中的beanDefinitionMap仅包含与类X相对应的BeanDefinition对象。

为了演示如何动态添加自定义的BeanDefinition对象,笔者首先手动创建了一个与类Y相对应的BeanDefinition实例,并通过调用registerBeanDefinition(“y”, yBeanDefinition)方法,将其注册到beanDefinitionMap中。这一步骤展示了如何向Spring容器中动态添加一个自行实例化的BeanDefinition对象。

随后,为了演示如何动态修改已扫描的BeanDefinition对象,笔者通过调用getBeanDefinition(“x”)方法获取了与类X相对应的已存在BeanDefinition对象。然后,通过调用xBeanDefinition.setBeanClassName(“Z”)方法,将原本与类X关联的BeanDefinition对象所指向的类更改为Z。这一步骤展示了如何在运行时动态修改Spring容器中已扫描和注册的BeanDefinition对象。

public static void main(String[] args) {AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext();ac.register(Appconfig.class);ac.refresh();//正常打印System.out.println(ac.getBean(Y.class));//正常打印System.out.println(ac.getBean(Z.class));//异常打印//虽然X加了注解,但是被偷梁换柱了,故而异常System.out.println(ac.getBean(X.class));}

总结上述图示内容,Spring实例化一个Bean的过程并非直接与你所提供的类相关,而是与特定的BeanDefinition对象所映射的类直接相关。通常情况下,一个BeanDefinition对象对应一个类,但特殊情况下也可能存在不同映射关系。为了更形象地解释这一点,可以借用一个比喻:假设读者你喜欢一位女性(小A),而小A则对笔者有所好感。然而,你不能因此就推断你也喜欢笔者,因为两者之间的喜好关系并不具有传递性。此外,需要强调的是,笔者在此仅作为比喻中的一个角色,与现实中的性别倾向无关,这一点应明确区分。

综上所述,BeanFactoryPostProcessor是Spring框架中一个强大的扩展点,允许程序员在Bean生命周期的特定阶段对BeanFactory进行深入的定制和修改。通过合理地利用这一特性,程序员可以构建出更加灵活、可维护的Spring应用。

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

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

相关文章

springboot websocket 知识点汇总

以下是一个详细全面的 Spring Boot 使用 WebSocket 的知识点汇总 1. 配置 WebSocket 添加依赖 进入maven官网, 搜索spring-boot-starter-websocket,选择版本, 然后把依赖复制到pom.xml的dependencies标签中 配置 WebSocket 创建一个配置类 WebSocketConfig&…

mysql不初始化升级

1、下载mysql,下载地址:MySQL :: Download MySQL Community Server 2、解压下载好的mysql,修改配置文件的datadir指定目录为当前数据存储的目录 3、通过管理员cmd进入新版本mysql的bin目录, 然后执行命令安装mysql服务&#xff…

Facebook软体机器人与机器人框架:创新社交互动的未来

随着人工智能技术的不断进步,Facebook正通过软体机器人和先进的机器人框架,重新定义社交互动的未来。这些创新不仅提升了用户体验,也为开发者提供了强大的工具来构建下一代社交应用。 一、Facebook软体机器人:智能化的社交伙伴 …

【学习笔记】无人机(UAV)在3GPP系统中的增强支持(七)-通过无人机实现无线接入的独立部署

引言 本文是3GPP TR 22.829 V17.1.0技术报告,专注于无人机(UAV)在3GPP系统中的增强支持。文章提出了多个无人机应用场景,分析了相应的能力要求,并建议了新的服务级别要求和关键性能指标(KPIs)。…

JVM:垃圾回收器

文章目录 一、介绍二、年轻代-Serial垃圾回收器三、老年代-SerialOld垃圾回收器四、年轻代-ParNew垃圾回收器五、老年代-CMS(Concurrent Mark Sweep)垃圾回收器六、年轻代-Parllel Scavenge垃圾回收器七、Parallel Old垃圾回收器八、G1垃圾回收器 一、介…

仅在少数市场发售?三星Galaxy Z Fold 6 Slim折叠屏手机更轻更薄

在智能手机的创新之路上,三星一直是行业的领跑者之一。随着Galaxy Z Fold系列的不断进化,三星再次突破技术边界,推出了更为轻薄的Galaxy Z Fold 6 Slim。 这款新型折叠屏手机以其独特的设计和卓越的性能,为用户带来了全新的使用体…

[终端安全]-8 隐私保护和隐私计算技术

1 隐私保护相关法规和标准 1)国内法规和标准 1.1)中华人民共和国网络安全法(2017年) - 规定了个人信息的保护和数据安全的基本原则。 - 要求网络运营者采取措施防止数据泄露、篡改和丢失。 1.2)信息安全技术&#x…

【Python】Gunicorn vs Uvicorn:如何选择适合你的 Python WSGI/ASGI 服务器

我白天是个 搞笑废物 表演不在乎 夜晚变成 忧伤怪物 撕扯着孤独 我曾经是个 感性动物 小心地感触 现在变成 无关人物 🎵 张碧晨/王赫野《何物》 在部署 Python Web 应用时,选择合适的服务器对应用的性能和稳定性至关重要。Gunicorn…

护眼台灯真的护眼吗?要注意学生如何正确使用台灯!

孩子们面临着越来越多的视力挑战,在近视学生中,近10%为高度近视,且占比随年级升高而增长。幼儿园6岁儿童中有1.5%为高度近视,而高中阶段则达到了17.6%。青少年是国家的未来和希望,而他们的视力健康却面临着前所未有的挑…

一篇讲清楚怎么选算力租赁平台

选择算力租赁平台时,需要考虑多个因素以确保找到最适合自己需求的服务。以下是一些关键点,可以帮助您做出明智的选择: 明确需求:首先,确定您的项目需要哪种类型的计算资源,比如CPU、GPU或FPGA,以…

Cadence23学习笔记(二)

原理图设计界面中就可以直接新建PCB: 亲测:需要画完原理图,并且DRC通过之后才可以! 放置完元器件之后要规定元件的Footprint ,注意PCB封装名要和库文件中的名字对应: DRC按钮: 点击图标 N, 生成第一网表&…

车载音视频MediaPlayer优化方案

媒体播放现状 从手机到车载,在很多地方还是有很大的不同。针对多媒体的场景Android车机目前大部分结构大致结构如下图: 从以上图看出的问题: 各个音视频APP单独实现播控界面,播放链路不一致,使用的底层播放器和音频焦…

基于Spring Boot的高校后勤餐饮管理系统

1 项目介绍 1.1 研究背景 “互联网”时代的到来,既给高校后勤管理发展带来了机遇,也带来了更大的挑战。信息化应用已经开始普及,传统的高校后勤餐饮管理模式往往存在着效率低下、信息不透明、资源浪费等问题,已经难以满足现代高…

Linux系统之部署经典魔塔小游戏

Linux系统之部署经典魔塔小游戏 一、魔塔小游戏介绍1.1 魔塔小游戏简介1.2 项目预览二、本次实践介绍2.1 本地环境规划2.2 本次实践介绍三、检查本地环境3.1 检查系统版本3.2 检查系统内核版本3.3 检查软件源四、安装Apache24.1 安装Apache2软件4.2 启动apache2服务4.3 查看apa…

如何使用断点续传方式上传大文件到阿里云 OSS

要使用断点续传方式上传大文件到阿里云 OSS,一般可以通过阿里云提供的 SDK 来实现。以下是使用 Java SDK 进行断点续传上传的示例代码,前提条件如下: 已创建存储空间(bucket)。具有oss:putObject权限。SDK 会将上传的状…

flask基础配置详情

前言 一个简单的应用 app Flask(__name__) app.route("/") def hello_world():return "<p>Hello,World!"运行Flask应用 #flask命令运行flask --app hello run#使用Python命令进行运行python -m flask # 作为一个捷径&#xff0c;如果文件名为 app…

STM32第十八课:SPIFlash

目录 需求一、SPI概要二、SPI配置1.开时钟2.配置IO3.配置&使能SPI 三、FLash操作函数1.SPI发送数据2.FLASH写使能3.FLASH等待操作完成4.FLASH页写操作5.FLASH读操作6.FLASH扇区擦除 四、需求实现 需求 通过SPI控制FLash进行数据的保存和删除。 一、SPI概要 在我们使用UA…

【python】OpenCV—European Article Number

参考学习来自&#xff1a;OpenCV基础&#xff08;25&#xff09;条码和二维码扫的生成与识别 1 条形码介绍 EAN-13是欧洲物品编码&#xff08;European Article Number&#xff09;的缩写&#xff0c;是一种广泛使用的条形码标准&#xff0c;特别是在超级市场和其它零售业中。…

【c++刷题笔记-动态规划】day38: 322. 零钱兑换 、 279.完全平方数 、139.单词拆分

322. 零钱兑换 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;完全背包&#xff0c;初始化为最大背包容量&#xff0c;当背包为0的时候有0枚硬币 重点&#xff1a;dp[i-coins[j]]1,统计个数 class Solution { public:int coinChange(vector<int>& coin…

OpenCV解决验证码(数字和字母)识别(Python)

文章目录 前言一、准备验证码图片 前言 OpenCV是一个基于Apache2.0许可&#xff08;开源&#xff09;发行的跨平台计算机视觉和机器学习软件库。它支持Windows、Linux、Mac OS、Android和iOS等多个操作系统&#xff0c;提供了丰富的图像处理和计算机视觉功能&#xff0c;包括但…