Spring IOC - Bean的生命周期之依赖注入
在Spring启动流程中,创建的factoryBean是DefaultListableBeanFactory,其类图如下所示:
可以看到其直接父类是AbstractAutoireCapableBeanFactory,他主要负责完成Bean的自动装配和创建工作。 具体来说,AbstractAutowireCapableBeanFactory会完成以下工作:
- 根据Bean的定义信息创建Bean实例;
- 根据Bean的定义信息完成Bean的依赖注入;
- 根据Bean的定义信息完成Bean的初始化工作;
- 返回创建好的Bean实例。
其中,属性注入是AbstractAutowireCapableBeanFactory的核心功能之一。它会根据Bean的定义信息,自动将依赖的Bean注入到当前Bean中。具体来说,它会根据Bean的依赖关系,自动查找并创建依赖的Bean实例,并将其注入到当前Bean中。
除了属性注入,AbstractAutowireCapableBeanFactory还支持构造函数注入、工厂方法注入等多种注入方式,可以满足不同场景下的需求。
其核心方法AbstractAutowireCapableBeanFactory#populateBean,下面,我们看一下populateBean方法是怎么进行依赖注入的。它处理Spring的各种依赖注入:包括自动注入(名称注入和类型注入)、注解注入(@Autowired和@Value等)、手动注入等。
-
首先,判断是否需要进行属性注入。调用 ibp#postProcessAfterInstantiation
-
自动注入:包括名称注入和类型注入。不推荐使用,只支持 XML 配置方式。
-
注解注入:处理 @Autowired 和 @Value 等注解,Spring 提供 ibp#postProcessProperties 可以调整 bean 实例。如 AutowiredAnnotationBeanPostProcessor 用于处理 @Autowired 和 @Value 注解。CommonAnnotationBeanPostProcessor 用于处理 @Resource 注解。
-
依赖检查:检查要注入的依赖是否已经完整。可以只检查简单类型(Java 原生类型、Enum、Class 等),也可以只检查对象类型。
-
手动注入:最基础的注入方式,实际上都委托给了 BeanWrapper 处理。
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {// 1. 判断是否需要进行属性注入:ibp#postProcessAfterInstantiationif (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof InstantiationAwareBeanPostProcessor) {InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {return;}}}}PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);// 2. 自动注入:包括名称注入和类型注入int resolvedAutowireMode = mbd.getResolvedAutowireMode();if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {MutablePropertyValues newPvs = new MutablePropertyValues(pvs);// 2.1 自动根据名称注入if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {autowireByName(beanName, mbd, bw, newPvs);}// 2.2 自动根据类型注入if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {autowireByType(beanName, mbd, bw, newPvs);}pvs = newPvs;}boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);// 3. 注解注入:后置处理器ibp#postProcessProperties,大名鼎鼎的@Autowired就是在这处理的。PropertyDescriptor[] filteredPds = null;if (hasInstAwareBpps) {if (pvs == null) {pvs = mbd.getPropertyValues();}for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof InstantiationAwareBeanPostProcessor) {InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {if (filteredPds == null) {filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);}pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);if (pvsToUse == null) {return;}}pvs = pvsToUse;}}}// 4. 依赖检查,循环依赖...if (needsDepCheck) {if (filteredPds == null) {filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);}checkDependencies(beanName, mbd, filteredPds, pvs);}// 5. 手动依赖注入if (pvs != null) {applyPropertyValues(beanName, mbd, bw, pvs);}
}
这里重点讲解AutowiredAnnotationBeanPostProcessor为代表的注解注入方式。其会调用到Spring进行依赖查找的核心API: beanFactory#resolveDependency,其本质是根据类型查找依赖,调用beanFactory#beanNamesForType方法根据类型查找依赖名称。他解决了以下四个场景:
-
Optional:JDK8 提供了 API。主要是将依赖设置非强制依赖,即 descriptor.required=false。
-
延迟依赖注入支持:ObjectFactory、ObjectProvider、javax.inject.Provider
-
另一种延迟注入的支持 - @Lazy 属性。
-
根据类型查找依赖 - doResolveDependency。
@Override
public Object resolveDependency(DependencyDescriptor descriptor, String requestingBeanName, Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {// ParameterNameDiscovery用于解析方法参数名称descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());// 1. Optional<T>if (Optional.class == descriptor.getDependencyType()) {return createOptionalDependency(descriptor, requestingBeanName);// 2. ObjectFactory<T>、ObjectProvider<T>} else if (ObjectFactory.class == descriptor.getDependencyType() ||ObjectProvider.class == descriptor.getDependencyType()) {return new DependencyObjectProvider(descriptor, requestingBeanName);// 3. javax.inject.Provider<T>} else if (javaxInjectProviderClass == descriptor.getDependencyType()) {return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);} else {// 4. @LazyObject result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(descriptor, requestingBeanName);// 5. 正常情况if (result == null) {result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);}return result;}
}
最底层都会调用beanFactory#doResolveDependency方法,其封装了依赖查找的各种情况。
-
快速查找: @Autowired 注解处理场景。AutowiredAnnotationBeanPostProcessor 处理 @Autowired 注解时,如果注入的对象只有一个,会将该 bean 对应的名称缓存起来,下次直接通过名称查找会快很多。
-
注入指定值:@Value 注解处理场景。QualifierAnnotationAutowireCandidateResolver 处理 @Value 注解时,会读取 @Value 对应的值进行注入。如果是 String 要经过三个过程:①占位符处理 -> ②EL 表达式解析 -> ③类型转换,这也是一般的处理过程,BeanDefinitionValueResolver 处理 String 对象也是这个过程。
-
集合依赖查询:直接全部委托给 resolveMultipleBeans 方法。
-
单个依赖查询:先调用 findAutowireCandidates 查找所有可用的依赖,如果有多个依赖,则根据规则匹配: @Primary -> @Priority -> ③方法名称或字段名称。
public Object doResolveDependency(DependencyDescriptor descriptor, String beanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);try {// 1. 快速查找,根据名称查找。AutowiredAnnotationBeanPostProcessor用到Object shortcut = descriptor.resolveShortcut(this);if (shortcut != null) {return shortcut;}// 2. 注入指定值,QualifierAnnotationAutowireCandidateResolver解析@Value会用到Class<?> type = descriptor.getDependencyType();Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);if (value != null) {if (value instanceof String) {// 2.1 占位符解析String strVal = resolveEmbeddedValue((String) value);BeanDefinition bd = (beanName != null && containsBean(beanName) ?getMergedBeanDefinition(beanName) : null);// 2.2 Spring EL 表达式value = evaluateBeanDefinitionString(strVal, bd);}TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());try {// 2.3 类型转换return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());} catch (UnsupportedOperationException ex) {return (descriptor.getField() != null ?converter.convertIfNecessary(value, type, descriptor.getField()) :converter.convertIfNecessary(value, type, descriptor.getMethodParameter()));}}// 3. 集合依赖,如 Array、List、Set、Map。内部查找依赖也是使用findAutowireCandidatesObject multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);if (multipleBeans != null) {return multipleBeans;}// 4. 单个依赖查询Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);// 4.1 没有查找到依赖,判断descriptor.requireif (matchingBeans.isEmpty()) {if (isRequired(descriptor)) {raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);}return null;}String autowiredBeanName;Object instanceCandidate;// 4.2 有多个,如何过滤if (matchingBeans.size() > 1) {// 4.2.1 @Primary -> @Priority -> 方法名称或字段名称匹配 autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);// 4.2.2 根据是否必须,抛出异常。注意这里如果是集合处理,则返回nullif (autowiredBeanName == null) {if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);} else {return null;}}instanceCandidate = matchingBeans.get(autowiredBeanName);} else {// We have exactly one match.Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();autowiredBeanName = entry.getKey();instanceCandidate = entry.getValue();}// 4.3 到了这,说明有且仅有命中一个if (autowiredBeanNames != null) {autowiredBeanNames.add(autowiredBeanName);}// 4.4 实际上调用 getBean(autowiredBeanName, type)。但什么情况下会出现这种场景?if (instanceCandidate instanceof Class) {instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);}Object result = instanceCandidate;if (result instanceof NullBean) {if (isRequired(descriptor)) {raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);}result = null;}if (!ClassUtils.isAssignableValue(type, result)) {throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());}return result;} finally {ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);}
}
这里重点看下看下第四种单个依赖的查询,集合依赖与其异曲同工:
-
查找容器中所有可用依赖:findAutowireCandidates 方法根据类型查找依赖。
-
如何有多个依赖怎么处理?其实 Spring 有一套通用的流程,先按 @Primary 查找,再按 @Priority,最后按方法名称或字段名称查找,直到只有一个 bean 为止。相关的匹配规则见 determineAutowireCandidate 方法。
-
此时只有一个依赖,从容器获取真实的 bean。descriptor.resolveCandidate 方法根据名称 autowiredBeanName 实例化对象。
从 findAutowireCandidates 方法,我们可以看到 Spring IoC 依赖注入的来源:
-
先查找 Spring IoC 内部依赖 resolvableDependencies。在 AbstractApplicationContext#prepareBeanFactory 方法中默认设置了如下内部依赖:BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext。
-
在父子容器进行类型查找:查找类型匹配的 beanNames,beanFactory#beanNamesForType 方法根据类型查找是,先匹配单例实例类型(包括 Spring 托管 Bean),再匹配 BeanDefinition 的类型。从这一步,我们可以看到 Spring 依赖注入的另外两个来源:一是 Spring 托管的外部 Bean,二是 Spring BeanDefinition。
protected Map<String, Object> findAutowireCandidates(@Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);// 1. Spring IoC 内部依赖 resolvableDependenciesfor (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {Class<?> autowiringType = classObjectEntry.getKey();if (autowiringType.isAssignableFrom(requiredType)) {Object autowiringValue = classObjectEntry.getValue();autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);if (requiredType.isInstance(autowiringValue)) {result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);break;}}}// 2. 类型查找:本质上递归调用beanFactory#beanNamesForType。先匹配实例类型,再匹配bd。String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this, requiredType, true, descriptor.isEager());for (String candidate : candidateNames) {// 2.1 isSelfReference说明beanName和candidate本质是同一个对象// isAutowireCandidate进一步匹配bd.autowireCandidate、泛型、@@Qualifier等进行过滤if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {// 2.2 添加到候选对象中addCandidateEntry(result, candidate, descriptor, requiredType);}}// 3. 补偿机制:如果依赖查找无法匹配,怎么办?包含泛型补偿和自身引用补偿两种。if (result.isEmpty()) {boolean multiple = indicatesMultipleBeans(requiredType);// 3.1 fallbackDescriptor: 泛型补偿,实际上是允许注入对象类型的泛型存在无法解析的情况DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();// 3.2 补偿1:不允许自称依赖,但如果是集合依赖,需要过滤非@Qualifier对象。什么场景?for (String candidate : candidateNames) {if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) &&(!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) {addCandidateEntry(result, candidate, descriptor, requiredType);}}// 3.3 补偿2:允许自称依赖,但如果是集合依赖,注入的集合依赖中需要过滤自己if (result.isEmpty() && !multiple) {for (String candidate : candidateNames) {if (isSelfReference(beanName, candidate) &&(!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) &&isAutowireCandidate(candidate, fallbackDescriptor)) {addCandidateEntry(result, candidate, descriptor, requiredType);}}}}return result;
}
findAutowireCandidates 大致可以分为三步:先查找内部依赖,再根据类型查找,最后没有可注入的依赖则进行补偿。
-
查找内部依赖:Spring IoC 容器本身相关依赖,这部分内容是用户而言是透明的,也不用感知。resolvableDependencies 集合中注册如 BeanFactory、ApplicationContext 、ResourceLoader、ApplicationEventPublisher 等。
-
根据类型查找:包括 ①外部托管 Bean ②注册 BeanDefinition。类型查找调用 beanFactory#beanNamesForType 方法。
-
自身引用:isSelfReference 方法判断 beanName 和 candidate 是否是同一个对象,包括两种情况:一是名称完全相同,二是 candidate 对应的工厂对象创建了 beanName。
-
是否可以注入:底层实际调用 resolver.isAutowireCandidate 方法进行过滤,包含三重规则:①bd.autowireCandidate=true -> ②泛型匹配 -> ③@Qualifier。下面会详细介绍这个方法。
-
-
补偿机制:如果依赖查找无法匹配,怎么办?Spring 提供了两种补偿机制:一是泛型补偿,允许注入对象对象的泛型无法解析,二是自身引用补偿,对这两种机制使用如下:
-
先使用泛型补偿,不允许自身引用:即 fallbackDescriptor。此时如果是集合依赖,对象必须是 @Qualifier 类型。
-
允许泛型补偿和自身引用补偿:但如果是集合依赖,必须过滤自己本身,即 beanName.equals(candidate) 必须剔除。
-
相关文章:

Spring IOC - Bean的生命周期之依赖注入
在Spring启动流程中,创建的factoryBean是DefaultListableBeanFactory,其类图如下所示: 可以看到其直接父类是AbstractAutoireCapableBeanFactory,他主要负责完成Bean的自动装配和创建工作。 具体来说,AbstractAutowire…...

Android Termux安装MySQL,内网穿透实现公网远程访问
文章目录 前言1.安装MariaDB2.安装cpolar内网穿透工具3. 创建安全隧道映射mysql4. 公网远程连接5. 固定远程连接地址 前言 Android作为移动设备,尽管最初并非设计为服务器,但是随着技术的进步我们可以将Android配置为生产力工具,变成一个随身…...

OpenCV快速入门:像素操作和图像变换
文章目录 前言1. 像素操作1.1 像素统计1.2 两个图像之间的操作1.2.1 图像加法操作1.2.3 图像加权混合 1.3 二值化1.4 LUT(查找表)1.4.1 查找表原理1.4.2 代码演示 2 图像变换2.1 旋转操作2.1.1 旋转的基本原理2.1.2 代码实现 2.2 缩放操作2.3 平移操作2.…...

Django 路由配置(二)
一、路由 就是根据用户请求的URL链接来判断对应的出来程序,并返回处理结果,也是就是URL和django的视图建立映射关系. 二、Django请求页面的步骤 1、首先Django确定要使用的根URLconf模块,通过ROOT_URLCONF来设置,在settings.py配置…...

电子学会C/C++编程等级考试2022年06月(一级)真题解析
C/C++等级考试(1~8级)全部真题・点这里 第1题:倒序输出 依次输入4个整数a、b、c、d,将他们倒序输出,即依次输出d、c、b、a这4个数。 时间限制:1000 内存限制:65536输入 一行4个整数a、b、c、d,以空格分隔。 0 < a,b,c,d < 108输出 一行4个整数d、c、b、a,整数之…...

【C++】使用std::vector()函数实现矩阵的加、减、点乘、点除等运算
本文通过vector()函数表示矩阵的形式,对 加、减、点乘、点除等运算进行编码和运行,相应结果如下文所述。 #include <iostream> #include <vector>using namespace std;// 矩阵加法 vector<vector<int>> …...

【python】直方图正则化详解和示例
直方图正则化(Histogram Normalization)是一种图像增强技术,目的是改变图像的直方图以改善图像的质量。具体来说,它通过将图像的直方图调整为指定的形状,以增强图像的对比度和亮度。 直方图正则化的基本步骤如下&…...

c语言:矩阵交换
题目: 代码和思路: #define _CRT_SECURE_NO_WARNINGS #include<stdio.h>int main() {int n 0;int m 0;int arr[10][10] { 0 }; // 输入行和列scanf("%d%d", &n, &m);int i 0;int j 0;//读取数组for (i 0; i < n; i)…...

【论文阅读】基于隐蔽带宽的汽车控制网络鲁棒认证(一)
文章目录 Abstract第一章 引言1.1 问题陈述1.2 研究假设1.3 贡献1.4 大纲 第二章 背景和相关工作2.1 CAN安全威胁2.1.1 CAN协议设计2.1.2 CAN网络攻击2.1.3 CAN应用攻击 2.2 可信执行2.2.1 软件认证2.2.2 消息身份认证2.2.3 可信执行环境2.2.4 Sancus2.2.5 VulCAN 2.3 侧信道攻…...

暖阳脚本_ 将Agent技术的灵活性引入RPA,清华等发布自动化智能体ProAgent
RPA暖阳脚本 近日,来自清华大学的研究人员联合面壁智能、中国人民大学、MIT、CMU 等机构共同发布了新一代流程自动化范式 “智能体流程自动化” Agentic Process Automation(APA),结合大模型智能体帮助人类进行工作流构建&#x…...

JUnit 单元自动化
一、Junit 是什么? Junit 是 Java 中用于单元测试的框架。使用 Junit 能让我们快速高效的完成单元测试。 自动化测试:JUnit提供了自动化测试的能力,开发人员可以编写一次测试用例,然后通过简单的命令或集成到持续集成工具中进行…...
Vue3 源码解读系列(十一)——插槽 slot
slot 插槽的实现实际上就是一种 延时渲染,把父组件中编写的插槽内容保存到一个对象上,并且把具体渲染 DOM 的代码用函数的方式封装,然后在子组件渲染的时候,根据插槽名在对象中找到对应的函数,然后执行这些函数做真正的…...

[github初学者教程] 分支管理-以及问题解决
作者:20岁爱吃必胜客(坤制作人),近十年开发经验, 跨域学习者,目前于新西兰奥克兰大学攻读IT硕士学位。荣誉:阿里云博客专家认证、腾讯开发者社区优质创作者,在CTF省赛校赛多次取得好成绩。跨领域…...
见面礼——图论
给定一个 n 个点 n 条边的无向图,你需要求有多少种选择图上的一个点 p 和一条边 (x,y) 的方案,使得删去 (x,y) 后图变成一棵树,且这棵树以 p 为根时每个节点的儿子个数均不超过 3。保证至少存在一种这样的方案。 Input 输入的第一行一个整数…...

【论文阅读】SPARK:针对视觉跟踪的空间感知在线增量攻击
SPARK: Spatial-Aware Online Incremental Attack Against Visual Tracking introduction 在本文中,我们确定了视觉跟踪对抗性攻击的一个新任务:在线生成难以察觉的扰动,误导跟踪器沿着不正确的(无目标攻击,UA&#x…...

MR混合现实教学系统在汽车检修与维护课堂教学中的应用
传统的汽车检修与维护课堂教学主要依赖教师口头讲解和黑板演示,这种方式存在一定的局限性。首先,对于一些复杂的机械结构和操作过程,教师难以生动形象地展示给学生。其次,学生无法直接观察到实际操作中的细节和注意事项࿰…...
CentOS7安装xvfb,解决服务器没有X-Server的问题
Linux服务器上一般没有图形界面,但是有时候有些软件又需要图形界面.比如oracle,自动化测试(puppeteer).运行的时候会提示没有没有X服务. 这时候一般不会去特地装图形界面.这个时候就要用xvfb来创建虚拟图形窗口. xvfb介绍 Xvfb(X Virtual Frame Buffer)是基于X Window的虚拟服…...

快速集成Skywalking 9(Windows系统、JavaAgent、Logback)
目录 一、Skywalking简介二、下载Skywalking服务端三、安装Skywalking服务端3.1 解压安装包3.2 启动Skywalking 四、关于Skywalking服务端更多配置五、Java应用集成skywalking-agent.jar5.1 下载SkyWalking Java Agent5.2 集成JavaAgent5.3 Logback集成Skywalking5.4 集成效果 …...

起立科技(起鸿)在第25届高交会上展示透明OLED技术创新
第二十五届中国国际高新技术成果交易会 日期:2023年11月15日 地点:福田会展中心7号馆 深圳,2023年11月15日 — 起鸿科技,作为透明OLED领域的引领者,于今日参展了第二十五届中国国际高新技术成果交易会。这一展会将汇…...
大模型LLM 在线量化;GPTQ\AWQ量化
1、大模型LLM 在线量化 参考:https://www.cnblogs.com/bruceleely/p/17348782.html ##8bit model = AutoModel.from_pretrained("THUDM/chatglm-6b", trust_remote_code=True).quantize(8).half(...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...

K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...

Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?
论文网址:pdf 英文是纯手打的!论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误,若有发现欢迎评论指正!文章偏向于笔记,谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...

Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...

前端开发面试题总结-JavaScript篇(一)
文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?2.解释 JavaScript 的作用域链(Scope Chain) 二、原型与继承3.原型链是什么?如何实现继承&a…...

网络编程(UDP编程)
思维导图 UDP基础编程(单播) 1.流程图 服务器:短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...