Spring IOC - Bean的生命周期之依赖注入

news2024/11/16 9:29:14

        在Spring启动流程中,创建的factoryBean是DefaultListableBeanFactory,其类图如下所示:

        可以看到其直接父类是AbstractAutoireCapableBeanFactory,他主要负责完成Bean的自动装配和创建工作。 具体来说,AbstractAutowireCapableBeanFactory会完成以下工作:

  1. 根据Bean的定义信息创建Bean实例;
  2. 根据Bean的定义信息完成Bean的依赖注入;
  3. 根据Bean的定义信息完成Bean的初始化工作;
  4. 返回创建好的Bean实例。

        其中,属性注入是AbstractAutowireCapableBeanFactory的核心功能之一。它会根据Bean的定义信息,自动将依赖的Bean注入到当前Bean中。具体来说,它会根据Bean的依赖关系,自动查找并创建依赖的Bean实例,并将其注入到当前Bean中。

         除了属性注入,AbstractAutowireCapableBeanFactory还支持构造函数注入、工厂方法注入等多种注入方式,可以满足不同场景下的需求。

        其核心方法AbstractAutowireCapableBeanFactory#populateBean,下面,我们看一下populateBean方法是怎么进行依赖注入的。它处理Spring的各种依赖注入:包括自动注入(名称注入和类型注入)、注解注入(@Autowired和@Value等)、手动注入等。

  1. 首先,判断是否需要进行属性注入。调用 ibp#postProcessAfterInstantiation

  2. 自动注入:包括名称注入和类型注入。不推荐使用,只支持 XML 配置方式。

  3. 注解注入:处理 @Autowired 和 @Value 等注解,Spring 提供 ibp#postProcessProperties 可以调整 bean 实例。如 AutowiredAnnotationBeanPostProcessor 用于处理 @Autowired 和 @Value 注解。CommonAnnotationBeanPostProcessor 用于处理 @Resource 注解。

  4. 依赖检查:检查要注入的依赖是否已经完整。可以只检查简单类型(Java 原生类型、Enum、Class 等),也可以只检查对象类型。

  5. 手动注入:最基础的注入方式,实际上都委托给了 BeanWrapper 处理。

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
    // 1. 判断是否需要进行属性注入:ibp#postProcessAfterInstantiation
    if (!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方法根据类型查找依赖名称。他解决了以下四个场景:

  1. Optional:JDK8 提供了 API。主要是将依赖设置非强制依赖,即 descriptor.required=false。

  2. 延迟依赖注入支持:ObjectFactory、ObjectProvider、javax.inject.Provider

  3. 另一种延迟注入的支持 - @Lazy 属性。

  4. 根据类型查找依赖 - 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. @Lazy
        Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
            descriptor, requestingBeanName);
        // 5. 正常情况
        if (result == null) {
            result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
        }
        return result;
    }
}

        最底层都会调用beanFactory#doResolveDependency方法,其封装了依赖查找的各种情况。

  1. 快速查找: @Autowired 注解处理场景。AutowiredAnnotationBeanPostProcessor 处理 @Autowired 注解时,如果注入的对象只有一个,会将该 bean 对应的名称缓存起来,下次直接通过名称查找会快很多。

  2. 注入指定值:@Value 注解处理场景。QualifierAnnotationAutowireCandidateResolver 处理 @Value 注解时,会读取 @Value 对应的值进行注入。如果是 String 要经过三个过程:①占位符处理 -> ②EL 表达式解析 -> ③类型转换,这也是一般的处理过程,BeanDefinitionValueResolver 处理 String 对象也是这个过程。

  3. 集合依赖查询:直接全部委托给 resolveMultipleBeans 方法。

  4. 单个依赖查询:先调用 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。内部查找依赖也是使用findAutowireCandidates
        Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
        if (multipleBeans != null) {
            return multipleBeans;
        }

        // 4. 单个依赖查询
        Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
        // 4.1 没有查找到依赖,判断descriptor.require
        if (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 根据是否必须,抛出异常。注意这里如果是集合处理,则返回null
            if (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);
    }
}

        这里重点看下看下第四种单个依赖的查询,集合依赖与其异曲同工:

  1. 查找容器中所有可用依赖:findAutowireCandidates 方法根据类型查找依赖。

  2. 如何有多个依赖怎么处理?其实 Spring 有一套通用的流程,先按 @Primary 查找,再按 @Priority,最后按方法名称或字段名称查找,直到只有一个 bean 为止。相关的匹配规则见 determineAutowireCandidate 方法。

  3. 此时只有一个依赖,从容器获取真实的 bean。descriptor.resolveCandidate 方法根据名称 autowiredBeanName 实例化对象。

        从 findAutowireCandidates 方法,我们可以看到 Spring IoC 依赖注入的来源:

  1. 先查找 Spring IoC 内部依赖 resolvableDependencies。在 AbstractApplicationContext#prepareBeanFactory 方法中默认设置了如下内部依赖:BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext。

  2. 在父子容器进行类型查找:查找类型匹配的 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 内部依赖 resolvableDependencies
    for (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 大致可以分为三步:先查找内部依赖,再根据类型查找,最后没有可注入的依赖则进行补偿。

  1. 查找内部依赖:Spring IoC 容器本身相关依赖,这部分内容是用户而言是透明的,也不用感知。resolvableDependencies 集合中注册如 BeanFactory、ApplicationContext 、ResourceLoader、ApplicationEventPublisher 等。

  2. 根据类型查找:包括 ①外部托管 Bean ②注册 BeanDefinition。类型查找调用 beanFactory#beanNamesForType 方法。

    1. 自身引用:isSelfReference 方法判断 beanName 和 candidate 是否是同一个对象,包括两种情况:一是名称完全相同,二是 candidate 对应的工厂对象创建了 beanName。

    2. 是否可以注入:底层实际调用 resolver.isAutowireCandidate 方法进行过滤,包含三重规则:①bd.autowireCandidate=true -> ②泛型匹配 -> ③@Qualifier。下面会详细介绍这个方法。

  3. 补偿机制:如果依赖查找无法匹配,怎么办?Spring 提供了两种补偿机制:一是泛型补偿,允许注入对象对象的泛型无法解析,二是自身引用补偿,对这两种机制使用如下:

    1. 先使用泛型补偿,不允许自身引用:即 fallbackDescriptor。此时如果是集合依赖,对象必须是 @Qualifier 类型。

    2. 允许泛型补偿和自身引用补偿:但如果是集合依赖,必须过滤自己本身,即 beanName.equals(candidate) 必须剔除。

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

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

相关文章

DSP2335的LED工程笔记

首先是确定时钟 在技术参考中&#xff0c;找到时钟章节 只能观察每个寄存器&#xff0c;才能看到寄存器控制那个外设的时钟 第二找到对应GPIO以及寄存器&#xff1b; 在我板子里面的原理图是 但是TI的提供的库函数是分ABC的&#xff0c;刚开始就不知道怎麽分。GPIO68到GPIO6…

Autox.js和Auto.js4.1.1手机编辑器不好用我自己写了一个编辑器

功能有 撤销 重做 格式化 跳转关键词 下面展示一些 内联代码片。 "ui"; ui.layout( <drawer id"drawer"><vertical><appbar><toolbar id"toolbar"title""h"20"/></appbar><horizontal b…

配置VNC环境时,出现xauth: file /root/.Xauthority does not exist的解决方案。

问题描述 在配置VNC&#xff08;Virtual Network Computing&#xff09;环境的过程时&#xff0c;首先安装了tigervnc-server包。在使用&#xff1a; vncserver命令创建VNC会话号的时候出现了一个报错&#xff1a;xauth: file /root/.Xauthority does not exist 原因分析&…

SpringBoot静态资源配置

项目中 SSM中配置 第一种&#xff1a;配置文件中 <mvc:resources mapping"/js/**" location"/js/"/> <mvc:resources mapping"/css/**" location"/css/"/> <mvc:resources mapping"/html/**" location&q…

python表白弹框

# codinggbk import tkinter as tk import random# 创建主窗口并隐藏 root tk.Tk() root.attributes(-alpha, 0) # 设置主窗口为不可见# 表白内容 message "cnmsb"# 创建弹框函数 def create_popup():x random.randint(0, root.winfo_screenwidth()) # 随机生成…

合并两个有序链表(冒泡排序实现)

实例要求&#xff1a;将两个升序链表合并为一个新的 升序 链表并返回&#xff1b;新链表是通过拼接给定的两个链表的所有节点组成的&#xff1b;实例分析&#xff1a;先拼接两个链表&#xff0c;在使用冒泡排序即可&#xff1b;示例代码&#xff1a; struct ListNode* mergeTwo…

EMD、EEMD、FEEMD、CEEMDAN分解的对比(其中CEEMDAN分解可以有效消除模态分解)

理论部分 EMD (Empirical Mode Decomposition)、EEMD (Ensemble EMD)、FEEMD (Fast Ensemble EMD) 和 CEEMDAN (Complete Ensemble EMD with Adaptive Noise) 是一些常用的信号分解方法&#xff0c;它们在信号分解的效果和特性上有所区别。 1. EMD&#xff1a; - EMD是最基础…

SpringBoot-AOP学习案例

4. AOP案例 SpringAOP的相关知识我们就已经全部学习完毕了。最后我们要通过一个案例来对AOP进行一个综合的应用。 4.1 需求 需求&#xff1a;将案例中增、删、改相关接口的操作日志记录到数据库表中 就是当访问部门管理和员工管理当中的增、删、改相关功能接口时&#xff0…

【MySQL--->用户管理】

文章目录 [TOC](文章目录) 一、用户管理表二、基本操作三、用户权限分配给用户某个数据库中某个表的某个权限. grant 权限 on 库.表名 to 用户名主机名. ![在这里插入图片描述](https://img-blog.csdnimg.cn/fe8eb171ef9343c3a09bd64d4f0db5c1.png)分配给用户某个数据库中全部表…

Adversarially Robust Neural Architecture Search for Graph Neural Networks

Adversarially Robust Neural Architecture Search for Graph Neural Networks----《面向图神经网络的对抗鲁棒神经架构搜索》 摘要 图神经网络&#xff08;GNN&#xff09;在关系数据建模方面取得了巨大成功。尽管如此&#xff0c;它们仍然容易受到对抗性攻击&#xff0c;这对…

Frames X for figma 组件库设计系统 Local Variables下载

简而言之&#xff0c;Frames X 是最出色、易于使用且文档齐全的Figma 设计系统之一。 它包括经过深思熟虑的设计指南和现成的组件&#xff0c;并且还提供一本全面的电子书&#xff0c;其中将详细解释如何使用该套件中包含的所有内容。 事实上&#xff0c;电子书使Frames X 与…

Spring IOC/DI和MVC及若依对应介绍

文章目录 一、Spring IOC、DI注解1.介绍2.使用 二、Spring MVC注解1.介绍2.使用 一、Spring IOC、DI注解 1.介绍 什么是Spring IOC/DI&#xff1f; IOC(Inversion of Control&#xff1a;控制反转)是面向对象编程中的一种设计原则。其中最常见的方式叫做依赖注入&#xff08;…

母婴服务预约小程序的效果如何

二胎家庭增速明显&#xff0c;占比较大&#xff0c;成为市场各母婴品牌的目标&#xff0c;而随着行业发展及市场变化&#xff0c;线上互联网深入人们生活&#xff0c;各家母婴品牌开始向“数字化”靠拢。 目前母婴门店商家主要面临服务/产品线上曝光不足、宣传度不够或扩圈无门…

C++ Sort函数详解

C Sort函数详解 前言 &#xff1a;sort函数是algorithm库下的一个函数&#xff0c;sort函数是不稳定的&#xff0c;即大小相同的元素在排序后相对顺序可能发生改变&#xff0c;如果某些场景需要保持相同元素间的相对顺序&#xff0c;可使用stable_sort函数&#xff0c;这里不过…

03 前后端数据交互【小白入门SpringBoot + Vue3】

项目笔记&#xff0c;教学视频来源于B站青戈 https://www.bilibili.com/video/BV1H14y1S7YV 前两个笔记。是把前端页面大致做出来&#xff0c;接下来&#xff0c;把后端项目搞一下。 后端项目&#xff0c;使用IDEA软件、jdk1.8、springboot2.x 。基本上用的是稳定版。 还有My…

【Linux】动静态库的使用与软链接的结合

文章目录 前言一、静态库1.静态库的创建2.静态库的链接3.将库进行打包4.链接方法&#xff1a;1.直接链接2.拷贝到系统路径里面3.采用软链接方法 二、动态库1.解决加载找不到动态库的方法1.直接拷贝2.建立软链接3.建立自己的动态路径配置文件 2.为什么动态库权限需可执行而静态库…

C#入门(1):程序结构、数据类型

一、C#程序结构 第一个C#程序 using System;namespace base_01 {class Program{#region 代码折叠块static void Main(string[] args){//控制台输出Console.WriteLine("Hello World!");Console.Write("C#是微软的编程语言"); //不换行输出//Console.Rea…

相机内参及其内参影响因素的几点思考

目录 理论推导之从相机坐标系到像素坐标系 机械参数对相机内参的影响 Resize与Crop对相机内参的影响 参考文献 理论推导之从相机坐标系到像素坐标系 相机内参的意义即从相机坐标系到像素坐标系的表达&#xff0c;其中共涉及到三个坐标系&#xff1a;相机坐标系、成像坐标系…

2023年【安全员-A证】报名考试及安全员-A证新版试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 安全员-A证报名考试是安全生产模拟考试一点通总题库中生成的一套安全员-A证新版试题&#xff0c;安全生产模拟考试一点通上安全员-A证作业手机同步练习。2023年【安全员-A证】报名考试及安全员-A证新版试题 1、【多选…

AIGC ChatGPT4 读取接口文件并进行可视化分析

数据分析的过程中,对数据文件进行可视化分析是每个数据分析师必备的技能。如下图数据源。 现在需要对各地区的销量进行汇总,使用Python来进行分析。 但是又不想写代码,或者不会Python代码,可以用ChatGPT4来帮我们完成代码的编写。 完整的Python代码: import pandas as p…