当前位置: 首页 > news >正文

源码分析Spring @Configuration注解如何巧夺天空,偷梁换柱。

前言

回想起五年前的一次面试,面试官问@Configuration注解和@Component注解有什么区别?记得当时的回答是:

  • 相同点:@Configuration注解继承于@Component注解,都可以用来通过ClassPathBeanDefinitionScanner装载Spring bean的配置信息。

  • 不同点:Component注解为通用组件类模式注解,Configuration注解为配置类模式注解,主要是在做代码分层上的有差别(当然也是从字面意思上理解)。

很显然不是面试官想要到答案,最后还是挂了。回去看了一下注解本身定,Configuration继承于Component,多了个proxyBeanMethods 属性,注释中提到在运行中可以生成子类进行增强,但是类类型必须不是final的,当proxyBeanMethods配置为false的时候不会进行增强。当时也就草率的下了定义,Configuration可以选择是否通过生成代理类进行增强。

进阶

多余的属性是proxyBeanMethods,字面的意思是代理Bean的方法,代理了个什么东西?是如何增强?带着问题写了一段测试代码:

@Component
public class Demo {public static void main(String[] args) {AnnotationConfigApplicationContext annotationConfigApplicationContext =new AnnotationConfigApplicationContext();annotationConfigApplicationContext.register(Demo.class);annotationConfigApplicationContext.refresh();Test test = annotationConfigApplicationContext.getBean(Test.class);System.out.println("spring ioc容器中管理的person对象:" + annotationConfigApplicationContext.getBean(Person.class));for (int i = 0; i < 2; i++) {System.out.println("通过bean作用的方法创建对象:" + test.createUser());}for (int i = 0; i < 2; i++) {System.out.println("通过没用bean作用的方法创建对象:" + test.createUserNoMethodBean());}annotationConfigApplicationContext.close();
}@Configurationpublic static class Test {@Beanpublic Person createUser() {return createUserNoMethodBean();}public Person createUserNoMethodBean(){Person person = new Person();person.setName(UUID.randomUUID().toString());return person;}}public static class Person {private String name;//get set 省略@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +'}';}}
}

代码输出

spring ioc容器中管理的person对象:Person{name='2a8c8844-c979-4784-942b-af4299d32866'}
通过bean作用的方法创建对象:Person{name='2a8c8844-c979-4784-942b-af4299d32866'}
通过bean作用的方法创建对象:Person{name='2a8c8844-c979-4784-942b-af4299d32866'}
通过没用bean作用的方法创建对象:Person{name='67707608-79bf-44c0-a83a-9ebaa7c17bb1'}
通过没用bean作用的方法创建对象:Person{name='27028430-9f82-4379-993a-6edd884ce145'}

我们调用Test对象创建用户方法,带有bean注解的返回的是同一个对象,并且与注入到spring ioc容器中的person对象是同一个,而且没有通过@Bean注解作用的方法真正执行了。不得不说好神奇。

猜想

我们先做一个大胆的猜想~ 注解Configuration注解proxyBeanMethods默认为true,也就是说默认会进行代理增强。调用通过bean注解的方法时会进行拦截,并且会舍弃调用真正的目标方法。其中会对带有bean的方法进行代理,对不带有bean的方法进行过滤。拦截带有bean方法返回对象时会从spring ioc容器中进行依赖查找并返回该对象。
如图流程图

  • 带有bean方法不需要进行执行目标方法,也就是我们的原始方法;
  • 我们需要注入我们的BeanFactory对象,来完成我们带有Bean方法依赖查找;
  • 需要对我们的方法进行过滤,需要对特定方法进行回调。
源码

由上一章我们熟悉了Spring ioc容器解析注册的流程,ConfigurationClassPostProcessor.class类比较重要,前半部分为BeanDefinitionRegistry逻辑,后半部为配置类的增强。
接下来开始查看对给定BeanFactoryPostProcessor的处理。跟踪源码到ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry(beanfactory);

/*** Prepare the Configuration classes for servicing bean requests at runtime* by replacing them with CGLIB-enhanced subclasses.*///通过 CGLIB 增强的子类来代替配置类来为 bean 请求提供支持
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {int factoryId = System.identityHashCode(beanFactory);if (this.factoriesPostProcessed.contains(factoryId)) {throw new IllegalStateException("postProcessBeanFactory already called on this post-processor against " + beanFactory);}this.factoriesPostProcessed.add(factoryId);if (!this.registriesPostProcessed.contains(factoryId)) {// BeanDefinitionRegistryPostProcessor hook apparently not supported...// Simply call processConfigurationClasses lazily at this point then.processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);}enhanceConfigurationClasses(beanFactory);beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}

继续跟踪到ConfigurationClassPostProcessor#enhanceConfigurationClasses(beanFactory)方法中,


public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {StartupStep enhanceConfigClasses = this.applicationStartup.start("spring.context.config-classes.enhance");Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();for (String beanName : beanFactory.getBeanDefinitionNames()) {BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);AnnotationMetadata annotationMetadata = null;MethodMetadata methodMetadata = null;if (beanDef instanceof AnnotatedBeanDefinition) {AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDef;annotationMetadata = annotatedBeanDefinition.getMetadata();methodMetadata = annotatedBeanDefinition.getFactoryMethodMetadata();}if ((configClassAttr != null || methodMetadata != null) && beanDef instanceof AbstractBeanDefinition) {// Configuration class (full or lite) or a configuration-derived @Bean method// -> eagerly resolve bean class at this point, unless it's a 'lite' configuration// or component class without @Bean methods.AbstractBeanDefinition abd = (AbstractBeanDefinition) beanDef;if (!abd.hasBeanClass()) {boolean liteConfigurationCandidateWithoutBeanMethods =(ConfigurationClassUtils.CONFIGURATION_CLASS_LITE.equals(configClassAttr) &&annotationMetadata != null && !ConfigurationClassUtils.hasBeanMethods(annotationMetadata));if (!liteConfigurationCandidateWithoutBeanMethods) {try {abd.resolveBeanClass(this.beanClassLoader);}catch (Throwable ex) {throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex);}}}}//判断BeanDefinition的configurationClass是否为full,然后加入集合后续进行特殊处理if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {if (!(beanDef instanceof AbstractBeanDefinition)) {throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +beanName + "' since it is not stored in an AbstractBeanDefinition subclass");}else if (logger.isInfoEnabled() && beanFactory.containsSingleton(beanName)) {logger.info("Cannot enhance @Configuration bean definition '" + beanName +"' since its singleton instance has been created too early. The typical cause " +"is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +"return type: Consider declaring such methods as 'static'.");}configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);}}if (configBeanDefs.isEmpty() || NativeDetector.inNativeImage()) {// nothing to enhance -> return immediatelyenhanceConfigClasses.end();return;}ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();//遍历进行cglib增强子类for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {AbstractBeanDefinition beanDef = entry.getValue();// If a @Configuration class gets proxied, always proxy the target classbeanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);// Set enhanced subclass of the user-specified bean classClass<?> configClass = beanDef.getBeanClass();Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);if (configClass != enhancedClass) {if (logger.isTraceEnabled()) {logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +"enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));}beanDef.setBeanClass(enhancedClass);}}enhanceConfigClasses.tag("classCount", () -> String.valueOf(configBeanDefs.keySet().size())).end();

ConfigurationClassEnhancer类中,我们看到增强的具体实现:


/*** Creates a new CGLIB {@link Enhancer} instance.*///创建cglib的实例。
private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {Enhancer enhancer = new Enhancer();//为增强类设置父类enhancer.setSuperclass(configSuperClass);//为增强类设置接口,该接口继承BeanFactoryAware,在实例化非lazy对象处理中接口回调阶段initializeBean    进行(BeanNameAware  ClassLoaderAware BeanFactoryAware 回调),我们能通过BeanFactoryAware  获取我们beanfactory类。enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});enhancer.setUseFactory(false);//设置beanfactory字段。方便进行依赖查找enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));//具体我们可以看到此处配置的filter 也就是特殊方法才会执行回调,否则调用父类目标方法enhancer.setCallbackFilter(CALLBACK_FILTER);enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());return enhancer;
}

CALLBACK_FILTER:并不是所有方法进行拦截, 首先需要拦截的是内部调用 @Bean 注解的方法时,进行ioc依赖查找返回;其次是依赖查找依赖的beanfactory字段的赋值;其他方法不进行拦截(当然也可以拦截,直接在调用父类的方法proxy.invokeSuper(obj, args));这样多实现不如不实现,没用!!!。

因为我们多个拦截器,所以我们需要进行组合,选出符合条件的拦截器下标。

/*** A {@link CallbackFilter} that works by interrogating {@link Callback Callbacks} in the order* that they are defined via {@link ConditionalCallback}.*/private static class ConditionalCallbackFilter implements CallbackFilter {private final Callback[] callbacks;private final Class<?>[] callbackTypes;//初始化的数据为固定顺序public ConditionalCallbackFilter(Callback[] callbacks) {this.callbacks = callbacks;this.callbackTypes = new Class<?>[callbacks.length];for (int i = 0; i < callbacks.length; i++) {this.callbackTypes[i] = callbacks[i].getClass();}}@Overridepublic int accept(Method method) {//遍历callbacks,首先判断是否符合for (int i = 0; i < this.callbacks.length; i++) {Callback callback = this.callbacks[i];if (!(callback instanceof ConditionalCallback) || ((ConditionalCallback) callback).isMatch(method)) {return i;}}throw new IllegalStateException("No callback available for method " + method.getName());}public Class<?>[] getCallbackTypes() {return this.callbackTypes;}}// The callbacks to use. Note that these callbacks must be stateless.
private static final Callback[] CALLBACKS = new Callback[] {new BeanMethodInterceptor(),new BeanFactoryAwareMethodInterceptor(),NoOp.INSTANCE
};
private static final ConditionalCallbackFilter CALLBACK_FILTER = new ConditionalCallbackFilter(CALLBACKS);

CALLBACKS 根据ConditionalCallbackFilter#accept(method)``方法逻辑,首先会判断是是继承了ConditionalCallback,然后调用isMatch(method)的方法;组合条件中 NoOp.INSTANCE非ConditionalCallback子类与BeanMethodInterceptor和BeanFactoryAwareMethodInterceptor互斥;BeanMethodInterceptorBeanFactoryAwareMethodInterceptor`互斥。

BeanMethodInterceptor#isMatch(method);源码

@Override
public boolean isMatch(Method candidateMethod) {return (candidateMethod.getDeclaringClass() != Object.class &&!BeanFactoryAwareMethodInterceptor.isSetBeanFactory(candidateMethod) &&BeanAnnotationHelper.isBeanAnnotated(candidateMethod));
}

接下来创建我们目标类的子类,注册子类的回调:


/**
*为派生子类的设置回调。
*/
private Class<?> createClass(Enhancer enhancer) {Class<?> subclass = enhancer.createClass();// Registering callbacks statically (as opposed to thread-local)// is critical for usage in an OSGi environment (SPR-5932)...//CALLBACKS为Enhancer.registerStaticCallbacks(subclass, CALLBACKS);return subclass;
}

CALLBACKS 为数组,

  • BeanMethodInterceptor:对于配置类中内部 @Bean 注解的方法的调用将会被拦截器拦截。拦截器的逻辑是判断声明的 Spring bean 在容器中是否已经存在,如果存在则直接返回容器中的 Spring bean。否则真正的配置类的方法创建 Spring bean 实例,避免了多例的出现。主要是解析获取我们beanfactory字段(该字段受益于CALLBACKS第二个元素的赋值),进行依赖查找。
private ConfigurableBeanFactory getBeanFactory(Object enhancedConfigInstance) {//解析BEAN_FACTORY_FIELD beanfactory字段~Field field = ReflectionUtils.findField(enhancedConfigInstance.getClass(), BEAN_FACTORY_FIELD);Assert.state(field != null, "Unable to find generated bean factory field");Object beanFactory = ReflectionUtils.getField(field, enhancedConfigInstance);Assert.state(beanFactory != null, "BeanFactory has not been injected into @Configuration class");Assert.state(beanFactory instanceof ConfigurableBeanFactory,"Injected BeanFactory is not a ConfigurableBeanFactory");return (ConfigurableBeanFactory) beanFactory;
}
  • BeanFactoryAwareMethodInterceptor:为我们代理类新增的BEAN_FACTORY_FIELD字段进行赋值。源码如下:
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {//查找我们的BEAN_FACTORY_FIELD字段Field field = ReflectionUtils.findField(obj.getClass(), BEAN_FACTORY_FIELD);Assert.state(field != null, "Unable to find generated BeanFactory field");//为我们字段进行赋值,args[0]->因为我们BeanFactoryAware接口回调方法为 void setBeanFactory(BeanFactory beanFactory) throws BeansException;只有一个参数~field.set(obj, args[0]);// Does the actual (non-CGLIB) superclass implement BeanFactoryAware?// If so, call its setBeanFactory() method. If not, just exit.//如果父类实现了BeanFactoryAware接口,改方法直接调用父类方法。if (BeanFactoryAware.class.isAssignableFrom(ClassUtils.getUserClass(obj.getClass().getSuperclass()))) {return proxy.invokeSuper(obj, args);}return null;
}
  • NoOp.INSTANCE:这个NoOp表示no operator,即什么操作也不做,代理类直接调用被代理的方法不进行拦截。
    配置中当然满足内部bean方法调用时走BeanMethodInterceptor;调用setBeanFactory(BeanFactory beanFactory)时走BeanFactoryAwareMethodInterceptor;其他方法走NoOp.INSTANCE。
    回到我们ConfigurationClassPostProcessor#enhanceConfigurationClasses(beanFactory)方法中,可以看到BeanDefinition的beanClass属性被赋值我们生成增强的代理子类,
    最后来到我们bean的实例化处理方法DefaultListableBeanFactory#preInstantiateSingletons。至此@Configuration的实现原理和我们猜想大致相同。

相关文章:

源码分析Spring @Configuration注解如何巧夺天空,偷梁换柱。

前言 回想起五年前的一次面试&#xff0c;面试官问Configuration注解和Component注解有什么区别&#xff1f;记得当时的回答是&#xff1a; 相同点&#xff1a;Configuration注解继承于Component注解&#xff0c;都可以用来通过ClassPathBeanDefinitionScanner装载Spring bean…...

vector的使用及模拟实现

目录 一.vector的介绍及使用 1.vector的介绍 2.vector的使用 1.vector的定义 2.vector iterator的使用 3. vector 空间增长问题 4.vector 增删查改 3.vector 迭代器失效问题&#xff08;重点&#xff09; 1. 会引起其底层空间改变的操作 2.指定位置元素的删除操作--erase 3. Li…...

“华为杯”研究生数学建模竞赛2007年-【华为杯】A题:基于自助法和核密度估计的膳食暴露评估模型(附获奖论文)

赛题描述 我国是一个拥有13亿人口的发展中国家,每天都在消费大量的各种食品,这批食品是由成千上万的食品加工厂、不可计数的小作坊、几亿农民生产出来的,并且经过较多的中间环节和长途运输后才为广大群众所消费,加之近年来我国经济发展迅速而环境治理没有能够完全跟上,以…...

刷题(第三周)

目录 [CISCN2021 Quals]upload [羊城杯 2020]EasySer [网鼎杯 2020 青龙组]notes [SWPU2019]Web4 [Black Watch 入群题]Web [HFCTF2020]BabyUpload [CISCN2021 Quals]upload 打开界面以后&#xff0c;发现直接给出了源码 <?php if (!isset($_GET["ctf"]))…...

新C++(14):移动语义与右值引用

当你在学习语言的时候&#xff0c;是否经常听到过一种说法,""左边的叫做左值&#xff0c;""右边的叫做右值。这句话对吗&#xff1f;从某种意义上来说&#xff0c;这句话只是说对了一部分。---前言一、什么是左右值?通常认为:左值是一个表示数据的表达式(…...

TCP相关概念

目录 一.滑动窗口 1.1概念 1.2滑动窗口存在的意义 1.3 滑动窗口的大小变化 1.4丢包问题 二.拥塞控制 三.延迟应答 四.捎带应答 五.面向字节流 六.粘包问题 七.TIME_WAIT状态 八.listen第2个参数 九.TCP总结 一.滑动窗口 1.1概念 概念&#xff1a;双方在进行通信时&a…...

MySQL锁篇

MySQL锁篇 一、一条update语句 我们的故事继续发展&#xff0c;我们还是使用t这个表&#xff1a; CREATE TABLE t (id INT PRIMARY KEY,c VARCHAR(100) ) EngineInnoDB CHARSETutf8;现在表里的数据就是这样的&#xff1a; mysql> SELECT * FROM t; —------- | id | c | —…...

SWF (Simple Workflow Service)简介

Amazon Simple Workflow Service (Amazon SWF) 提供了给应用程序异步、分布式处理的流程工具。 SWF可以用在媒体处理、网站应用程序后端、商业流程、数据分析和一系列定义好的任务上。 举个例子&#xff0c;下图表明了一个电商网站的工作流程&#xff0c;其中涉及了程序执行的…...

java(Class 常用方法 获取Class对象六种方式 动态和静态加载 类加载流程)

ClassClass常用方法获取Class对象六种方式哪些类型有Class对象动态和静态加载类加载流程加载阶段连接阶段连接阶段-验证连接阶段-准备连接阶段-解析初始化阶段获取类结构信息Class常用方法 第一步&#xff1a;创建一个实体类 public class Car {public String brand "宝…...

【数据结构】线性表和顺序表

Yan-英杰的主页 悟已往之不谏 知来者之可追 目录 1.线性表 2.顺序表 2.1 静态顺序表 2.2 动态顺序表 2.3移除元素 1.线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线…...

Ubuntu数据库安装(mysql)

##1.下载mysql-apt-config_0.8.22-1_all.deb并且安装 wget https://dev.mysql.com/get/mysql-apt-config_0.8.22-1_all.deb sudo dpkg -i mysql-apt-config_0.8.22-1_all.deb##2.更新apt-updata sudo apt update##3.如果出现如下图情况执行以下命令 [外链图片转存失败,源站可…...

MyBatis-Plus的入门学习

MyBatis-Plus入门学习简介特性快速开始MyBatis-Plus的注解详解Tableld主键生成策略1、数据库自动增长 AUTO2、UUID3、Redis生成id4、MP主键自动生成TableNameTableField自动填充测试方法&#xff1a;update乐观锁select查所有根据id查多个id批量查询简单条件查询&#xff08;通…...

华为OD机试题 - 内存池(JavaScript)

更多题库,搜索引擎搜 梦想橡皮擦华为OD 👑👑👑 更多华为OD题库,搜 梦想橡皮擦 华为OD 👑👑👑 更多华为机考题库,搜 梦想橡皮擦华为OD 👑👑👑 华为OD机试题 最近更新的博客使用说明本篇题解:内存池题目输入输出示例一输入输出说明Code解题思路版权说明华为…...

数据库索引原理

数据库索引的作用是做数据的快速检索&#xff0c;而快速检索实现的本质是数据结构。像二叉树、红黑树、AVL树、B树、B树、哈希等数据结构都可以实现索引&#xff0c;但其中B树效率最高。MySQL数据库索引使用的是B树。二叉树&#xff1a;二叉树中&#xff0c;左子树比根节点小&a…...

字符函数和字符串函数详解(1)

目录前言strlen函数strlensizeofstrcpy函数strcat函数strcmp函数总结前言 最近要调整状态&#xff0c;写的文章质量不佳让大家失望&#xff0c;我现在也在反思我在做什么&#xff0c;我会什么&#xff0c;我学了什么。等我想明白的那天&#xff0c;我一定能跟大家顶峰相见的&a…...

【数据分析:工具篇】NumPy(1)NumPy介绍

【数据分析&#xff1a;工具篇】NumPy&#xff08;1&#xff09;NumPy介绍NumPy介绍NumPy的特点数组的基本操作创建数组索引和切片数组运算NumPy介绍 NumPy&#xff08;Numerical Python&#xff09;是Python的一个开源的科学计算库&#xff0c;它主要用于处理大规模的多维数组…...

mysql时区问题

设置mysql容器时间与服务器时间一致 问题背景&#xff1a; 今天测试发现一个问题&#xff0c;时间不一致&#xff0c;当工单入库时&#xff0c;其创建时间和更新时间应该是一样的&#xff0c;即使不一样最多只会错几秒的时间&#xff1b;实际上两个时间相差的大概8小时&#…...

磨金石教育摄影技能干货分享|高邮湖上观花海

江苏高邮&#xff0c;说到这里所有人能想到的&#xff0c;就是那烟波浩渺的高邮湖。高邮在旅游方面并不出名&#xff0c;但是这里的自然人文景观绝对不输于其他地方。高邮不止有浩瀚的湖泊&#xff0c;春天的油菜花海同样壮观。春日的午后&#xff0c;与家人相约游玩&#xff0…...

mysql navicat忘记密码

mysql忘记密码是常用的事情&#xff0c;那么如何解决它呢&#xff1f;1、首先将MySQL的服务关闭&#xff0c;两种方法&#xff1a;&#xff08;1&#xff09;打开命令行cmd输入net stop mysql命令即可关闭MySQL服务。&#xff08;2&#xff09;打开任务管理器&#xff0c;找到服…...

Git的下载、安装、配置、使用、卸载

前言 我是跟着狂神老师学的。该博客仅用于笔记所用。 下面是老师的B站和笔记 B站&#xff1a;https://www.bilibili.com/video/BV1FE411P7B3?p1&vd_source9266cf72b1f398b63abe0aefe358d7d6 笔记&#xff1a;https://mp.weixin.qq.com/s/Bf7uVhGiu47uOELjmC5uXQ 一、准备工…...

【论文笔记】若干矿井粉尘检测算法概述

总的来说&#xff0c;传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度&#xff0c;通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

《基于Apache Flink的流处理》笔记

思维导图 1-3 章 4-7章 8-11 章 参考资料 源码&#xff1a; https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

AGain DB和倍数增益的关系

我在设置一款索尼CMOS芯片时&#xff0c;Again增益0db变化为6DB&#xff0c;画面的变化只有2倍DN的增益&#xff0c;比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析&#xff1a; 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...

Selenium常用函数介绍

目录 一&#xff0c;元素定位 1.1 cssSeector 1.2 xpath 二&#xff0c;操作测试对象 三&#xff0c;窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四&#xff0c;弹窗 五&#xff0c;等待 六&#xff0c;导航 七&#xff0c;文件上传 …...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

Ubuntu系统复制(U盘-电脑硬盘)

所需环境 电脑自带硬盘&#xff1a;1块 (1T) U盘1&#xff1a;Ubuntu系统引导盘&#xff08;用于“U盘2”复制到“电脑自带硬盘”&#xff09; U盘2&#xff1a;Ubuntu系统盘&#xff08;1T&#xff0c;用于被复制&#xff09; &#xff01;&#xff01;&#xff01;建议“电脑…...

Vue 模板语句的数据来源

&#x1f9e9; Vue 模板语句的数据来源&#xff1a;全方位解析 Vue 模板&#xff08;<template> 部分&#xff09;中的表达式、指令绑定&#xff08;如 v-bind, v-on&#xff09;和插值&#xff08;{{ }}&#xff09;都在一个特定的作用域内求值。这个作用域由当前 组件…...

Python 高级应用10:在python 大型项目中 FastAPI 和 Django 的相互配合

无论是python&#xff0c;或者java 的大型项目中&#xff0c;都会涉及到 自身平台微服务之间的相互调用&#xff0c;以及和第三发平台的 接口对接&#xff0c;那在python 中是怎么实现的呢&#xff1f; 在 Python Web 开发中&#xff0c;FastAPI 和 Django 是两个重要但定位不…...

路由基础-路由表

本篇将会向读者介绍路由的基本概念。 前言 在一个典型的数据通信网络中&#xff0c;往往存在多个不同的IP网段&#xff0c;数据在不同的IP网段之间交互是需要借助三层设备的&#xff0c;这些设备具备路由能力&#xff0c;能够实现数据的跨网段转发。 路由是数据通信网络中最基…...

可视化预警系统:如何实现生产风险的实时监控?

在生产环境中&#xff0c;风险无处不在&#xff0c;而传统的监控方式往往只能事后补救&#xff0c;难以做到提前预警。但如今&#xff0c;可视化预警系统正在改变这一切&#xff01;它能够实时收集和分析生产数据&#xff0c;通过直观的图表和警报&#xff0c;让管理者第一时间…...