@RefreshScope源码解析
前言
@RefeshScope这个注解想必大家都用过,在微服务配置中心的场景下经常出现,它可以用来刷新Bean中的属性配置,那么它是如何做到的呢?让我们来一步步揭开它神秘的面纱。
RefreshScope介绍
就是说我们在修改了bean属性的时候项目不需要重新启动,就可以拿到最新的值。
我们先来看下@RefreshScope的接口
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Scope("refresh")
@Documented
public @interface RefreshScope {/*** @see Scope#proxyMode()* @return proxy mode*///创建基于类的代理(使用 CGLIB)ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;}
可以看出其是一个复合注解,被标注了 @Scope(“refresh”),@RefreshScope 是scopeName="refresh"的 @Scope
我们来看下org.springframework.cloud.context.scope.refresh.RefreshScope类的关系图
- Scope -> GenericScope -> RefreshScope
RefreshScope管理了Scope=Refresh的Bean的生命周期,提供了get(获取),refreshAll(刷新)、destory(销毁)等方法
源码解析
版本说明
<spring-boot.version>2.6.3</spring-boot.version><spring-cloud.version>2021.0.1</spring-cloud.version>
Bean创建过程
我们创建一个带@RefreshScope注解的类
@Data
@Component
@RefreshScope
@ConfigurationProperties(prefix = "test")
public class TestProperties {private String name ;}
启动项目进行debugger跟踪
invokeBeanFactoryPostProcessors
AbstractApplicationContext#invokeBeanFactoryPostProcessors方法中
调用Bean Factory的后置处理器,从上面的类图中我们可以看到RefreshScope就是一个BeanFactoryPostProcessors
然后调用父类GenericScope的postProcessBeanDefinitionRegistry方法
该方法遍历所有的bean定义 如果当前的bean的scope为refresh,那么就把当前的bean设置为 LockedScopedProxyFactoryBean的代理对象。
@RefreshScope标注的类还有一个特点:会使用代理对象并进行延迟加载。我们来看一下postProcessBeanDefinitionRegistry方法
@RefreshScope 注解的 bean,除了会生成一个beanName的 bean,同时会生成 scopedTarget.beanName的 bean
所以如果有@ConditionalOnSingleCandidate 注解的 bean,就不能在使用@RefreshScope的注解了。因为@ConditionalOnSingleCandidate全局只能有一个此类型的 bean
RefreshScope还会监听一个ContextRefreshedEvent,该事件会在ApplicationContext初始化或者refreshed时触发
ContextRefreshedEvent事件
AbstractApplicationContext#finishRefresh方法中
// 上下文刷新事件publishEvent(new ContextRefreshedEvent(this));
我们来看一下RefreshScope中的代码:
@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {start(event);}public void start(ContextRefreshedEvent event) {if (event.getApplicationContext() == this.context && this.eager && this.registry != null) {eagerlyInitialize();}}private void eagerlyInitialize() {for (String name : this.context.getBeanDefinitionNames()) {BeanDefinition definition = this.registry.getBeanDefinition(name);if (this.getName().equals(definition.getScope()) && !definition.isLazyInit()) {Object bean = this.context.getBean(name);if (bean != null) {bean.getClass();}}}}
如果这个bean的scope = refresh的话就会去执行getBean方法,我们可以看到bean的名字为scopedTarget.testProperties这是一个被代理过的bean
doGetBean
上面的this.context.getBean(name)
中会使用BeanFactory的doGetBean方法创建Bean,不同scope有不同的创建方式:
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)throws BeansException {String beanName = transformedBeanName(name);Object beanInstance;...// 创建单例beanif (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, mbd, args);}catch (BeansException ex) {// Explicitly remove instance from singleton cache: It might have been put there// eagerly by the creation process, to allow for circular reference resolution.// Also remove any beans that received a temporary reference to the bean.destroySingleton(beanName);throw ex;}});beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}// 创建原型beanelse if (mbd.isPrototype()) {// It's a prototype -> create a new instance.Object prototypeInstance = null;try {beforePrototypeCreation(beanName);prototypeInstance = createBean(beanName, mbd, args);}finally {afterPrototypeCreation(beanName);}beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);}// Scope 类型创建beanelse {String scopeName = mbd.getScope();if (!StringUtils.hasLength(scopeName)) {throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");}Scope scope = this.scopes.get(scopeName);if (scope == null) {throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");}try {//使用RefreshScope父类的get方法,然后使用ConcurrentMap缓存下来Object scopedInstance =scope.get(beanName, () -> {//把Bean信息存储到ThreadLocal变量中beforePrototypeCreation(beanName);try {return createBean(beanName, mbd, args);}finally {//把Bean信息从ThreadLocal变量中移除afterPrototypeCreation(beanName);}});beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);}catch (IllegalStateException ex) {throw new ScopeNotActiveException(beanName, scopeName, ex);}}}catch (BeansException ex) {beanCreation.tag("exception", ex.getClass().toString());beanCreation.tag("message", String.valueOf(ex.getMessage()));cleanupAfterBeanCreationFailure(beanName);throw ex;}finally {beanCreation.end();}}return adaptBeanInstance(name, beanInstance, requiredType);}
- 单例和原型的Bean都是硬编码写在代码里面的
- 除了单例和原型Bean,其他Scope是由Scope对象处理的
- 使用RefreshScope父类的get方法,然后使用ConcurrentMap缓存下来
然后执行createBean创建Bean,创建Bean还是由IOC来做(createBean方法),但是获取Bean,都由RefreshScope对象的get方法去获取,其get方法在父类GenericScope中。GenericScope 实现了 Scope 最重要的 get(String name, ObjectFactory objectFactory) 方法,在GenericScope 里面 包装了一个内部类 BeanLifecycleWrapperCache。来对加了 @RefreshScope 创建的对象进行缓存,使其在不刷新时获取的都是同一个对象。(这里你可以把 BeanLifecycleWrapperCache 想象成为一个大Map 缓存了所有@RefreshScope 标注的对象)
GenericScope中get方法
@Overridepublic Object get(String name, ObjectFactory<?> objectFactory) {BeanLifecycleWrapper value = this.cache.put(name, new BeanLifecycleWrapper(name, objectFactory));this.locks.putIfAbsent(name, new ReentrantReadWriteLock());try {return value.getBean();}catch (RuntimeException e) {this.errors.put(name, e);throw e;}}public Object getBean() {if (this.bean == null) {synchronized (this.name) {if (this.bean == null) {this.bean = this.objectFactory.getObject();}}}return this.bean;}
BeanLifecycleWrapper这个是@RefreshScope标记bean的一个包装类,会被存储到缓存里,在这里取不到值的话就会从objectFactory里去拿,也就是重新创建一个去执行doCreateBean方法
动态刷新Bean的配置变量值
当配置中心刷新配置之后,有两种方式可以动态刷新Bean的配置变量值,(SpringCloud-Bus还是Nacos差不多都是这么实现的)
- 向上下文发布一个RefreshEvent事件
- Http访问/refresh这个EndPoint (RefreshEndpoint)
RefreshEventListener 监听器
当我们发布一个RefreshEvent事件的时候,RefreshEventListener就会监听到,然后调用handle处理
@Overridepublic void onApplicationEvent(ApplicationEvent event) {if (event instanceof ApplicationReadyEvent) {handle((ApplicationReadyEvent) event);}else if (event instanceof RefreshEvent) {handle((RefreshEvent) event);}}...public void handle(RefreshEvent event) {if (this.ready.get()) { // don't handle events before app is readylog.debug("Event received " + event.getEventDesc());//调用ContextRefresher的refresh方法Set<String> keys = this.refresh.refresh();log.info("Refresh keys changed: " + keys);}}
ContextRefresher
调用refresh方法
public synchronized Set<String> refresh() {Set<String> keys = refreshEnvironment();// RefreshScope调用刷新方法this.scope.refreshAll();return keys;}public synchronized Set<String> refreshEnvironment() {//把原来的配置都读取出来放到map里面Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());//更新环境updateEnvironment();//获取新老之间有差异的属性源key集合Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet();//发布EnvironmentChangeEvent事件,这个事件是ConfigurationPropertiesRebinder监听的this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));return keys;}
updateEnvironment(更新环境)
- 先拷贝出基础的环境属性
- 通过SpringApplicationBuilder构建了一个简单的SpringBoot启动程序
- 这里面会添加两个监听器分别为:BootstrapApplicationListener与ConfigFileApplicationListener,BootstrapApplicationListener是引导程序的核心监听器,而ConfigFileApplicationListener主要就是读取配置文件的
- 然后调用run方法启动,就是springboot的run方法。使用非WEB的方式启动
- 然后读取最新的配置出来遍历,如果不是基础属性配置的环境并且老的环境和新的环境有一样的就替换成新的环境
@Overrideprotected void updateEnvironment() {addConfigFilesToEnvironment();}/* For testing. */ ConfigurableApplicationContext addConfigFilesToEnvironment() {ConfigurableApplicationContext capture = null;try {//拷贝出基础的环境环境属性,如系统环境变量,Java的属性StandardEnvironment environment = copyEnvironment(getContext().getEnvironment());Map<String, Object> map = new HashMap<>();map.put("spring.jmx.enabled", false);map.put("spring.main.sources", "");// gh-678 without this apps with this property set to REACTIVE or SERVLET failmap.put("spring.main.web-application-type", "NONE");map.put(BOOTSTRAP_ENABLED_PROPERTY, Boolean.TRUE.toString());environment.getPropertySources().addFirst(new MapPropertySource(REFRESH_ARGS_PROPERTY_SOURCE, map));SpringApplicationBuilder builder = new SpringApplicationBuilder(Empty.class).bannerMode(Banner.Mode.OFF).web(WebApplicationType.NONE).environment(environment);// Just the listeners that affect the environment (e.g. excluding logging// listener because it has side effects)builder.application().setListeners(Arrays.asList(new BootstrapApplicationListener(), new BootstrapConfigFileApplicationListener()));capture = builder.run();if (environment.getPropertySources().contains(REFRESH_ARGS_PROPERTY_SOURCE)) {environment.getPropertySources().remove(REFRESH_ARGS_PROPERTY_SOURCE);}//老的配置MutablePropertySources target = getContext().getEnvironment().getPropertySources();String targetName = null;//遍历新配置for (PropertySource<?> source : environment.getPropertySources()) {String name = source.getName();if (target.contains(name)) {targetName = name;}//老的环境不包含基础的配置if (!this.standardSources.contains(name)) {//老的环境里面有新环境的配置if (target.contains(name)) {//替换target.replace(name, source);}else {if (targetName != null) {target.addAfter(targetName, source);// update targetName to preserve orderingtargetName = name;}else {// targetName was null so we are at the start of the listtarget.addFirst(source);targetName = name;}}}}}finally {ConfigurableApplicationContext closeable = capture;while (closeable != null) {try {closeable.close();}catch (Exception e) {// Ignore;}if (closeable.getParent() instanceof ConfigurableApplicationContext) {closeable = (ConfigurableApplicationContext) closeable.getParent();}else {break;}}}return capture;}
changes(新老键值比较)
找出改变过的值放到result里面
private Map<String, Object> changes(Map<String, Object> before, Map<String, Object> after) {Map<String, Object> result = new HashMap<String, Object>();for (String key : before.keySet()) {if (!after.containsKey(key)) {result.put(key, null);}else if (!equal(before.get(key), after.get(key))) {result.put(key, after.get(key));}}for (String key : after.keySet()) {if (!before.containsKey(key)) {result.put(key, after.get(key));}}return result;}
EnvironmentChangeEvent事件
由ConfigurationPropertiesRebinder来处理这个事件。调用rebind方法进行配置重新加载,rebind方法实际上就是先销毁再去创建Bean。这里会遍历所有带@ConfigurationProperties注解的Bean,但是并不包含有@RefreshScope注解的。
destroyBean:如果这个Bean实现了DisposableBean接口,就是执行destroy方法。或者是有实现AutoCloseable接口就进行资源关闭操作
initializeBean:bean初始化的操作
@Overridepublic void onApplicationEvent(EnvironmentChangeEvent event) {if (this.applicationContext.equals(event.getSource())// Backwards compatible|| event.getKeys().equals(event.getSource())) {rebind();}}@ManagedOperationpublic void rebind() {this.errors.clear();for (String name : this.beans.getBeanNames()) {rebind(name);}}@ManagedOperationpublic boolean rebind(String name) {if (!this.beans.getBeanNames().contains(name)) {return false;}if (this.applicationContext != null) {try {Object bean = this.applicationContext.getBean(name);if (AopUtils.isAopProxy(bean)) {bean = ProxyUtils.getTargetObject(bean);}if (bean != null) {// TODO: determine a more general approach to fix this.// see https://github.com/spring-cloud/spring-cloud-commons/issues/571if (getNeverRefreshable().contains(bean.getClass().getName())) {return false; // ignore}this.applicationContext.getAutowireCapableBeanFactory().destroyBean(bean);this.applicationContext.getAutowireCapableBeanFactory().initializeBean(bean, name);return true;}}catch (RuntimeException e) {this.errors.put(name, e);throw e;}catch (Exception e) {this.errors.put(name, e);throw new IllegalStateException("Cannot rebind to " + name, e);}}return false;}
ContextRefresher#refreshAll
发布事件完以后就执行 RefreshScope中的refreshAll
public void refreshAll() {super.destroy();this.context.publishEvent(new RefreshScopeRefreshedEvent());}
super.destroy()调用父类的destroy方法。这里把cache缓存给清理掉。
wrapper.destroy();
缓存中的bean清除了,但是这些bean还需要销毁。
public void destroy() {if (this.callback == null) {return;}synchronized (this.name) {Runnable callback = this.callback;if (callback != null) {callback.run();}this.callback = null;this.bean = null;}}
然后当我们项目中有使用到被@RefreshScope注释的Bean的时候,在doGetBean方法中从GenericScope中的cache缓存中获取不到的话就会重新去创建Bean。这样获取到的就是最新的值了。
获取类的中属性时,会重新调用doGetBean的
总结
- 被@RefreshScope标注的Bean在创建的时候是会生产一个代理对象
- 当发布RefreshEvent事件时,会调用ContextRefresher#refresh方法,该方法会记录当前的环境,然后构建一个非web的SpringApplicationBuilder并执行其run方法。
- 通过新旧环境的比较找出修改过的属性。changes操作来变更已有的PropertySource。
- 通过EnvironmentChangeEvent事件把缓存中清除
- 再次获取对象的时候重新创建,从新的属性环境中读取最新值
相关文章:

@RefreshScope源码解析
前言 RefeshScope这个注解想必大家都用过,在微服务配置中心的场景下经常出现,它可以用来刷新Bean中的属性配置,那么它是如何做到的呢?让我们来一步步揭开它神秘的面纱。 RefreshScope介绍 就是说我们在修改了bean属性的时候项目…...

【开发】后端框架——Spring
前置知识:JSP&Servlet 学习视频:https://www.bilibili.com/video/BV1WE411d7Dv?spm_id_from333.999.0.0 IoC:控制反转 IoC的理解:IoC思想,IoC怎么创建对象,IoC是Spring的核心 依赖注入三种方式&#x…...

vue中的自定义指令
前言 说到 vue 中的自定义指令,相信大家都不陌生。在官网中是这么说的,除了核心功能默认内置的指令 (v-model 和 v-show),vue 也允许注册自定义指令。那什么时候会用到自定义指令呢?代码复用和抽象的主要形式是组件。然而…...

技术分享及探讨
前言 很高兴给大家做一个技术分享及探讨。 下面给大家分享几个工作遇到有趣的例子。 docker docker 进程 现象 客户的模型导入到BML平台发布预测服务后,模型本身是用django提供的支持。按照本地docker的方式进行调试,kill掉django的进程修改代码…...

人工智能AI
AI 模型。它使用深度神经网络,从数十亿或数万亿个单词中学习,能够生成任何主题或领域的文本。它可以执行各种自然语言任务,如分类、总结、翻译、生成和对话。 大语言模型开发建立在4个核心思想上: 模型 – Models 提示词 - Prompt…...
2022天梯赛补题
题目详情 - L2-041 插松枝 (pintia.cn) 思路:模拟 背包就是个栈,开个stack解决流程思路是,每次取推进器前,尽可能拿背包的,背包拿到不可以时,跳出拿推进器时判断: 如果背包装得下,…...

字节跳动测试岗面试挂在2面,复盘后,我总结了失败原因,决定再战一次...
先说下我基本情况,本科不是计算机专业,现在是学通信,然后做图像处理,可能面试官看我不是科班出身没有问太多计算机相关的问题,因为第一次找工作,字节的游戏专场又是最早开始的,就投递了…...

Nodejs实现通用的加密和哈希算法(MD5、SHA1、Hmac、AES、Diffie-Hellman、RSA),crypto模块详解
crypto crypto模块的目的是为了提供通用的加密和哈希算法(hash)。用纯JavaScript代码实现这些功能不是不可能,但速度会非常慢。Nodejs用C/C++实现这些算法后,通过cypto这个模块暴露为JavaScript接口,这样用起来方便,运行速度也快。 MD5和SHA1 MD5是一种常用的哈希算法,…...

测试行业3年经验,从大厂裸辞后,面试阿里、字节全都一面挂,被面试官说我的水平还不如应届生
测试员可以先在大厂镀金,以后去中小厂毫无压力,基本不会被卡,事实果真如此吗?但是在我身上却是给了我很大一巴掌... 所谓大厂镀金只是不卡简历而已,如果面试答得稀烂,人家根本不会要你。况且要不是大厂出来…...

安卓悬浮窗口, 丝滑双指缩放视频窗口
最重要的事情说前面: demo源码:https://github.com/5800LDW/ProjectFloatingWindow前言:1.跨应用的浮动窗口在网上很多资料, 就不细说了。2.双指缩放View 也很多资料, 可参考:https://blog.csdn.net/zxq614/article/details/88873729正文下面进入正题, 如何把上述结合起来, 下面…...

300左右哪款蓝牙耳机适合学生用?四款便宜质量好的蓝牙耳机推荐
近年来,随着蓝牙耳机的发展,不管是音质、外观、佩戴还是降噪都有了很大的提升。但是我们在入手蓝牙耳机时,最好还是根据预算和需求入手。在此,我来给预算在三百内的朋友推荐几款便宜质量好的蓝牙耳机,可以当个参考。 …...

桥梁设计模式
介绍 Java桥梁模式(也称桥接模式)(Bridge Pattern)是一种设计模式,它将抽象和实现分离,使它们可以独立地变化.它通过一个大类或者一系列紧密关联的类拆分成两个独立的层次结构来实现这种分离,其中一个层次结构包含抽象类或接口,另一个层次结构包含实现类.桥梁模式使得抽象类和…...

【华为OD机试 2023最新 】 新员工座位(C++)
文章目录 题目描述输入描述输出描述用例题目解析C++题目描述 工位由序列F1,F2…Fn组成,Fi值为0、1或2。其中0代表空置,1代表有人,2代表障碍物。 1、某一空位的友好度为左右连续老员工数之和, 2、为方便新员工学习求助,优先安排友好度高的空位, 给出工位序列,求所有空…...

蓝桥杯刷题第二十二天
第一题:受伤的皇后题目描述有一个 nn 的国际象棋棋盘(n 行 n 列的方格图),请在棋盘中摆放 n 个受伤的国际象棋皇后,要求:任何两个皇后不在同一行。任何两个皇后不在同一列。如果两个皇后在同一条 45 度角的…...

CentOS从gcc 4.8.5 升级到gcc 8.3.1
gcc -v查看当前gcc版本。 sudo yum install centos-release-scl-rh安装centos-release-scl-rh。 sudo yum install devtoolset-8-build安装devtoolset-8-build。 显示“Complete!”表示安装成功。 sudo yum install devtoolset-8-gdb安装devtoolset-8-gdb。 显示“Comple…...

【人人都能读标准】12. 原始类型的编码形式
本文为《人人都能读标准》—— ECMAScript篇的第12篇。我在这个仓库中系统地介绍了标准的阅读规则以及使用方式,并深入剖析了标准对JavaScript核心原理的描述。 ECMAScript有7种原始类型,分别是Undefined、Null、Boolean、String、Number、BigInt、Symbo…...

VUE进行前后端交互
目录 一、 跨域 1. 什么是跨域? 2. 什么是本域? 3. 浏览器请求的三种报错 二、SpringBoot解决跨域问题其他前后端跨域请求解决方案 1. SpringBoot上直接添加CrossOrigin 2. 处理跨域请求的Configuration 3. 采用过滤器的方式 3.1 方式一 3.2 方式…...

ThingsBoard Gateway:物联网设备数据采集与集成的强大解决方案
文章目录ThingsBoard Gateway:物联网设备数据采集与集成的强大解决方案1\. ThingsBoard Gateway:概述2\. 主要特点与优势3\. 应用场景4\. 如何使用ThingsBoard Gateway:物联网设备数据采集与集成的强大解决方案 随着物联网(IoT&a…...

什么是镜像/raid
镜像(Mirroring)是一种文件存储形式,是冗余的一种类型,一个磁盘上的数据在另一个磁盘上存在一个完全相同的副本即为镜像。可以把许多文件做成一个镜像文件,与GHOST等程序放在一个盘里用GHOST等软件打开后,又…...

【Python】如何有效比较两个时间序列在图形上的相似度?
文章目录前言一、1.准备二、实操1.使用Matplotlib可视化比较两个时间序列2.计算两个时间序列的相关系数:3.使用Python实现动态时间规整算法(DTW):总结前言 比较两个时间序列在图形上是否相似,可以通过以下方法&#x…...

JavaEE-常见的锁策略和synchronized的锁机制
目录常见的锁策略乐观锁和悲观锁轻量级锁和重量级锁自旋锁和挂起等待锁普通互斥锁和读写锁公平锁和非公平锁可重入锁和不可重入锁synchronized的锁机制synchronized特性锁升级/锁膨胀锁消除锁粗化常见的锁策略 乐观锁和悲观锁 乐观锁和悲观锁主要是看主要是锁竞争的激烈程度.…...

信息化,数字化,智能化是三种不同的概念吗?
前两年流行“信息化”,网上铺天盖地都是关于“信息化”的文章,这两年开始流行起“数字化”,于是铺天盖地都是“数字化”的文章。(这一点从数字化和信息化这两个关键词热度趋势就可以看出来)。 但点开那些文章仔细看看…...

【华为OD机试 2023最新 】 匿名信(C++ 100%)
题目描述 电视剧《分界线》里面有一个片段,男主为了向警察透露案件细节,且不暴露自己,于是将报刊上的字减下来,剪拼成匿名信。 现在又一名举报人,希望借鉴这种手段,使用英文报刊完成举报操作。 但为了增加文章的混淆度,只需满足每个单词中字母数量一致即可,不关注每个…...

硬件语言Verilog HDL牛客刷题day05 时序逻辑部分
1.VL29 信号发生器 1.题目: 题目描述: 请编写一个信号发生器模块,根据波形选择信号wave_choise发出相应的波形:wave_choice0时,发出方波信号;wave_choice1时,发出锯齿波信号;wave…...

Ajax 入门
前端技术:在浏览器中执行的程序都是前端技术。如 html、css、js 等 后端技术:在服务器中执行的长须,使用 Java 等语言开发的后端程序。servlet,jsp,jdbc,mysql,tomacat 等 全局刷新 使用表单…...

半导体器件基础06:发光二极管
说在开头:关于玻尔原子模型(1) 卢瑟福的模型面临着与经典电磁波理论的矛盾,按照经典电磁波理论,卢瑟福的原子不可能稳定存在超过1秒钟。玻尔面临着选择:要么放弃卢瑟福模型,要么放弃麦克斯韦伟…...

AutoCV第二课:Python基础
目录Python基础前言1.流程控制1.1 条件语句1.2 循环语句1.2.1 while循环语句1.2.2 for循环语句1.3 作业1.4 拓展-try except语法2.函数2.1 函数定义2.2 函数的参数2.2.1 位置参数2.2.2 命名参数2.2.3 默认参数2.2.4 可变参数2.2.5 参数展开2.3 递归函数2.3.1 递归函数定义2.3.2…...

LeetCode算法 打家劫舍 和 打家劫舍II C++
目录题目 打家劫舍参考答案题目 打家劫舍II参考答案题目 打家劫舍 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯…...

蓝桥杯刷题冲刺 | 倒计时10天
作者:指针不指南吗 专栏:蓝桥杯倒计时冲刺 🐾马上就要蓝桥杯了,最后的这几天尤为重要,不可懈怠哦🐾 文章目录1.有边数限制的最短路2.九进制转十进制1.有边数限制的最短路 题目 链接: 853. 有边数…...

个人练习-Leetcode-剑指 Offer II 109. 开密码锁
题目链接:https://leetcode.cn/problems/zlDJc7/ 题目大意:给出一个四位数字的密码锁,初始状态是0000,目标是targer。每一次转动只能让一个位的轮盘转动一下(0往后转是9)。有一个vector<string> dea…...