Spring Boot 面试题——自动装配

news2024/11/25 16:33:43

目录

  • Spring Boot 中的自动装配是指什么?解决了什么问题?
  • Spring Boot 自动装配的流程有哪些?
  • Spring Boot 中实现自动装配的原理是什么?
  • Spring 中的 ConfigurationClassPostProcessor 类有什么作用?
  • Spring Boot 自动装配涉及的核心源码有哪些?
    • Spring Boot 应用程序启动类
    • 执行 SpringApplication 类中的构造方法
    • 执行 SpringApplication 类中的 run 方法
      • 执行 run 方法中的 prepareContext 方法
      • 执行 run 方法中的 refreshContext 方法
  • META-INF/spring.factories 文件有什么作用?

Spring Boot 中的自动装配是指什么?解决了什么问题?

(1)在 Spring Boot 中,自动装配是一种机制,它允许开发者无需显式地配置依赖项或组件,而是通过约定和默认值来实现自动配置。通过这种方式,Spring Boot能够简化开发过程并提高开发效率。

(2)自动装配能够解决以下问题:

  • 依赖管理:Spring Boot 的自动配置机制能够根据项目的需求自动引入所需的依赖项,而无需手动维护和管理依赖关系。
  • 配置管理:Spring Boot 提供了一种简化的配置方式,可以根据约定和默认值自动装配应用程序所需的配置参数,减少了繁琐的配置过程。
  • 组件注册:Spring Boot 可以自动扫描项目中的组件,并将其注册为 Spring 容器中的 Bean,使得这些组件能够被其他组件自动注入和使用。
  • 功能扩展:Spring Boot 提供了大量的自动配置模块,可以根据项目的需要自动启用或禁用某些功能,实现快速开发和定制。

(3)总的来说,自动装配的实现就是为了从 META-INF/spring.factories 文件中加载并创建对应的 Bean 对象,并且将它们交给 Spring 容器来进行统一管理。

Spring Boot 自动装配的流程有哪些?

(1)Spring Boot 的自动装配流程如下:

  • Spring Boot 启动时,会扫描并加载所有的类路径下的 META-INF/spring.factories 文件,并读取其中的配置信息。
  • 通过读取 spring.factories 文件中 org.springframework.boot.autoconfigure.EnableAutoConfiguration 键对应的值,获取所有需要自动装配的配置类
  • Spring Boot 根据这些配置类的信息,按照特定的顺序进行自动装配。关键的自动装配注解有 @EnableAutoConfiguration@SpringBootApplication
  • 自动装配过程中,Spring Boot 会根据配置类中的条件注解(以 @ConditionalOnXXX 开头)来进行判断,决定是否装配某个组件。
  • 自动装配过程还会考虑用户自定义的配置,可以通过在 application.propertiesapplication.yml 文件中添加配置信息,来覆盖默认的自动装配行为
  • 最后,Spring Boot 会根据自动装配的结果和应用的需要,创建相应的 Bean,并将其注册到 Spring 的应用上下文中,供应用程序使用

(2)总的来说,Spring Boot 的自动装配机制通过扫描配置文件,加载配置类,并根据配置类和条件进行自动装配,从而简化了应用程序的配置工作。

Spring Boot 中实现自动装配的原理是什么?

(1)Spring Boot 中实现自动装配的原理入如下:

  • 在启动类中的执行 SpringApplication.run 方法,并且将启动类的 class 对象作为参数传入其中,方便 Spring Boot 能够扫描该类所在的包以及其子包,并解析该启动类上的注解。
  • 加载 spring.factories:当启动 Spring Boot 应用程序的时候,会先创建 SpringApplication 对象,并且在对象的构造方法中会进行一些初始化工作,最主要的是判断当前应用程序的类型以及设置初始化器和监听器,并且在这个过程中会加载整个应用程序中 META-INF/spring.factories 文件中的配置类,将文件的内容放到缓存对象中,方便后续获取。
  • 执行两个关键方法SpringApplication 对象创建完成之后,开始执行 run 方法,来完成整个启动,启动过程中有两个非常重要的方法,分别是 prepareContext 方法和 refreshContext 方法,在这两个关键步骤中完整了自动装配的核心功能:
    • prepareContext 方法中主要完成的是对上下文对象的初始化操作,包括了属性值的设置,比如环境对象,在整个过程中有一个非常重要的方法,叫做 load,它主要完成一件事,将当前启动类做为一个 beanDefinition 对象注册到 registry 中,方便后续在进行 BeanFactoryPostProcessor 调用执行的时候,找到对应的主类,来完成 @SpringBootApplicaiton 等注解的解析工作;
    • refreshContext 方法中会进行整个容器的刷新过程,会调用 AbstractApplicationContext 类中的 refresh 方法,该方法中有 13 个非常关键的方法,来完成整个 Spring 应用程序的启动。在自动装配过程中,会调用 13 个方法中的 invokeBeanFactoryPostProcessor 方法,此方法主要是对 ConfigurationClassPostProcessor 类的处理,该类实现了 BeanDefinitionRegisterProcessor 接口(BeanFactoryPostProcessor 的子接口)。在调用的时候会调用该类中的 postProcessBeanDefinitionRegistry 方法,该方法解析处理各种注解,包含 @PropertySource@ComponentScan@ComponentScans@Bean@lmport 等注解,最主要的是 @lmport 注解的解析:
      • 在解析 @lmport 注解的时候,会有一个 getImports 的方法,从主类开始递归解析注解,把所有包含 @lmport 的注解都解析到,然后在processImport 方法中对 Import 的类进行分类,其中 AutoConfigurationImportSelect 归属于 ImportSelect 的子类,在后续过程中会调用 deferredlmportSelectorHandler 中的 process 方法,来完整 EnableAutoConfiguration 的加载;

Spring 中的 ConfigurationClassPostProcessor 类有什么作用?

(1)在 Spring 中,ConfigurationClassPostProcessor类是一个重要的后置处理器,它实现了 BeanDefinitionRegisterProcessor 接口(BeanFactoryPostProcessor 的子接口,具体关系如下图所示),其作用是处理带有 @Configuration 注解的配置类。具体而言,ConfigurationClassPostProcessor 类完成以下几个主要的任务:

  • 将配置类转换为BeanDefinition对象:当Spring Boot应用启动时,ConfigurationClassPostProcessor会检查所有带有@Configuration注解的配置类,并将它们转换为BeanDefinition对象。这个过程包括解析配置类中的各种元数据,如 Bean 的定义、作用域、依赖关系等,并将其存储在Spring的BeanDefinition注册表中。
  • 处理配置类中的特殊注解:ConfigurationClassPostProcessor 还会处理配置类中的一些特殊注解,比如@Conditional@Import@PropertySource 等。它会根据这些注解的定义,对应进行处理并最终影响 Bean 的创建和配置。
  • 处理配置类中的 @Bean 注解:配置类中通常使用 @Bean 注解来定义 Bean 的创建和配置,而ConfigurationClassPostProcessor 会解析这些 @Bean 方法,将其转换为对应的 BeanDefinition 对象,并存储在 Spring 容器的 Bean 定义注册表中。这样,当应用程序需要该 Bean 时,Spring 容器就可以根据这些注册信息进行创建和管理。

(2)总体来说,ConfigurationClassPostProcessor 类在 Spring Boot 中起到了非常重要的作用。它负责解析和处理配置类,将其转换为Spring 容器可以理解的 BeanDefinition 对象,并最终完成 Bean 的创建和配置过程。通过该后置处理器,Spring Boot 可以高效地管理和配置应用程序中的各种 Bean。

在这里插入图片描述

Spring Boot 自动装配涉及的核心源码有哪些?

Spring Boot 应用程序启动类

执行 SpringApplication 中的 run 方法,并且将启动类的 class 对象作为参数传入其中,方便 Spring Boot 能够扫描该类所在的包以及其子包,并解析该启动类上的注解。

@SpringBootApplication
public class ContentApplication {
    public static void main(String[] args) {
        SpringApplication.run(ContentApplication.class, args);
    }
}

执行 SpringApplication 类中的构造方法

SpringApplication 类的构造方法中会进行一些初始化工作,最重要的是加载 spring.factories 文件:

public class SpringApplication {
	
	//...
	
	public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}

	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

	public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
	}
	
	//执行 SpringApplication 类中的构造方法
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		//设置初始化器,此过程中会加载 spring.factories 文件
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		//设置监听器
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}	

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
		// loadFactoryNames 方法中会加载 spring.factories 文件,具体执行步骤在 SpringFactoriesLoader 类中
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}
}
public final class SpringFactoriesLoader {
	
	//...
	
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
	
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}

	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {	
			//下面的 FACTORIES_RESOURCE_LOCATION 即为 spring.factories 文件的路径
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}
}

执行 SpringApplication 类中的 run 方法

SpringApplication 类中 run 方法是整个过程的核心方法。

public class SpringApplication {

	//...

	public ConfigurableApplicationContext run(String... args) {	
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		//上下文对象,保存当前操作前后的属性信息
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			//***准备上下文***
			prepareContext(context, environment, listeners, applicationArguments, printedBanner);
			//***刷新上下文***
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, exceptionReporters, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}
}

执行 run 方法中的 prepareContext 方法

public class SpringApplication {
	
	//...

	private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
		context.setEnvironment(environment);
		postProcessApplicationContext(context);
		applyInitializers(context);
		listeners.contextPrepared(context);
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
		if (printedBanner != null) {
			beanFactory.registerSingleton("springBootBanner", printedBanner);
		}
		if (beanFactory instanceof DefaultListableBeanFactory) {
			((DefaultListableBeanFactory) beanFactory)
					.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
		}
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
		// Load the sources
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		/*
			(1) sources 中保存了启动类 ContentApplication 的相关信息
			(2) laod 方法将当前启动类做为一个 BeanDefinition 对象注册到 registry 中,方便后面解析到启动类上的注解
		*/
		load(context, sources.toArray(new Object[0]));
		listeners.contextLoaded(context);
	}

	protected void load(ApplicationContext context, Object[] sources) {
		if (logger.isDebugEnabled()) {
			logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
		}
		BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
		if (this.beanNameGenerator != null) {
			loader.setBeanNameGenerator(this.beanNameGenerator);
		}
		if (this.resourceLoader != null) {
			loader.setResourceLoader(this.resourceLoader);
		}
		if (this.environment != null) {
			loader.setEnvironment(this.environment);
		}
		loader.load();
	}
}

执行 run 方法中的 refreshContext 方法

public class SpringApplication {
	
	//...

	private void refreshContext(ConfigurableApplicationContext context) {
		if (this.registerShutdownHook) {
			try {
				context.registerShutdownHook();
			}
			catch (AccessControlException ex) {
				// Not allowed in some environments.
			}
		}
		refresh((ApplicationContext) context);
	}
}

上面的方法会调用到 AbstractApplicationContext 类中的 refresh 方法:

public abstract class AbstractApplicationContext extends DefaultResourceLoader
		implements ConfigurableApplicationContext {
	
	//...
	
	@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

			finally {
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			}
		}
	}
}

META-INF/spring.factories 文件有什么作用?

(1)META-INF/spring.factories 文件是在使用 Spring 框架的项目中常见的一个文件,它用于进行 Spring 框架的扩展和自动装配。spring.factories 文件采用键值对的形式,其中键表示一个接口或抽象类的全限定名,值表示具体的实现类或配置类的全限定名。这个文件的作用是告诉 Spring 框架在启动时自动加载和注册这些实现类或配置类,从而进行自动装配。

(2)下面是 Spring Boot 框架中 spring.factories 文件的部分内容:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

//...

在这里插入图片描述

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

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

相关文章

关于微软文本转语音(语音合成)的一些坑

1. 单个音频时长限制10分钟 文档地址 2. 多人配音SSML 每次请求 <voice> 标签只能最大50个&#xff0c;参考 #1 3. SDK 在 linux 环境下 报错&#xff1a;gcc 软件无法加载 4. 语音品质问题 使用 SDK 生成的音频声音很差&#xff0c;默认音频流格式为 WAV&#xf…

Java——接口类和抽象类的方法声明不需要加{}

在Java中&#xff0c;接口类和抽象类的方法声明是不需要加{}的。具体来说&#xff1a; 1. 接口类&#xff08;Interface&#xff09;&#xff1a;接口类定义了一组方法的规范&#xff0c;没有具体的方法实现。在接口中&#xff0c;方法声明只包含方法名、参数列表和返回类型&a…

vue2.0 打包,nginx部署

1、修改这里为空 否则报错&#xff1a;vue is undefined 2、修改为hash&#xff0c;重点&#xff1a;打包dist文件运行&#xff0c;必须这样 3、安装ngnix&#xff0c;重点&#xff1a;使用node的包&#xff1a;httpserve&#xff0c;失败 4、重点&#xff1a;配置代理转发 前端…

au如何分离人声和背景音乐?帮你整理了几个必备的!

Audition专为在照相室、广播设备和后期制作设备方面工作的音频和视频专业人员设计&#xff0c;可提供先进的音频混合、编辑、控制和效果处理功能。因为软件的专业性&#xff0c;所以有些功能使用起来就比较复杂&#xff0c;下面介绍了详细的操作步骤。 方法一&#xff1a;直接使…

许战海战略文库|全球车型定位与中国车企的全球化机会

引言&#xff1a;从丰田阿尔法的身份转变到沃尔沃的市场双重身份,再到各大洲皮卡文化的多样表达,我们可以发现&#xff0c;同一车型在不同区域角色认知存在差异,不同区域主流化车型存在巨大差异。而背后是一系列关于文化差异、消费理念和生活方式的区别。中国新能源车企正面临一…

Spring Cloud之Sentinel的简单学习

目录 雪崩问题 超时处理 线程隔离 熔断降级 流量控制 服务对比 安装Sentinel控制台 案例 簇点链路 限流规则 流控模式 直接模式 关联模式 链路模式 流控效果 Warm up 排队等待 热点参数限流 隔离与降级 Feign整合Sentinel 线程隔离 规则设置 熔断降级 …

CSGO游戏里的饰品是如何被炒作起来的?

csgo倒狗们是如何操盘csgo饰品市场的&#xff1f; CSGO游戏里的饰品是如何被炒作起来的&#xff1f; 随着近几年csgo玩家数量急剧上升&#xff0c;倒狗在市场中的比例也在上升&#xff0c;之前的csgo饰品市场以散户居多&#xff0c;价格波动不大&#xff0c;现在倒狗大量涌入&a…

如何选择微信管理系统?

如何选择微信管理系统&#xff1f; 1、不用下载安装软件&#xff0c;不越狱不刷机 2、不绑定手机或电脑&#xff0c;不对电脑或手机做限制&#xff0c;也不受电脑、手机关闭、关机影响 3、能更新迭代&#xff0c;不限制版本 4、使用安全登录&#xff0c;保障账号安全的 5、不用…

Java方法与递归

目录 前言 1.什么是方法 2.语法的定义 3.方法调用的执行过程 4.实参和形参的关系 5.没有返回值的方法 5.方法的重载 为什么会有方法的重载&#xff1f; 方法重载概念 5.递归 递归的概念 递归执行过程分析 递归练习 前言 在我们日常在代码的过程中&#xff0c;会遇到…

大厂的 Git 代码管理规范是怎样的?

分支命名 master 分支 master 为主分支&#xff0c;也是用于部署生产环境的分支&#xff0c;需要确保 master 分支稳定性。master 分支一般由 release 以及 hotfix 分支合并&#xff0c;任何时间都不能直接修改代码。 develop 分支 develop 为开发环境分支&#xff0c;始终…

【Cortex-A7】 常用汇编指令

【Cortex-A7】 常用汇编指令 Cortex-A7 常用汇编指令1. 处理器内部数据传输2. 存储器访问指令3. 压栈和出栈指令4. 跳转指令5. 算术运算指令6. 逻辑运算指令 Cortex-A7 常用汇编指令 参考《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》的 A4 章节。 …

Android工具栏ToolBar

主流APP除了底部有一排标签栏外&#xff0c;通常顶部还有一排导航栏。在Android5.0之前&#xff0c;这个顶部导航栏以ActionBar控件的形式出现&#xff0c;但AcionBar存在不灵活、难以扩展等毛病&#xff0c;所以Android5.0之后推出了ToolBar工具栏控件&#xff0c;意在取代Aci…

ENVI波段合成

1、envi5.3合成&#xff08;这种方法&#xff0c;必须有地理参考才可以&#xff09; 在工具栏处搜索波段&#xff0c;找到波段合成&#xff08;Layer Stacking&#xff09; 设置合成波段&#xff0c;其他默认 2、envi classic&#xff08;没有地理坐标也可以&#xff09; 我们…

直击“挑战杯”竞赛:解锁顶级企业的“创新密码”

“企业只有一项真正的资源——人。” 管理学大师德鲁克&#xff0c;曾发表这样的观点。言外之意&#xff0c;人才是企业发展的根本动力&#xff0c;因为人才可以提供源源不断的创新力。能够基业长青、持续前行的企业&#xff0c;一定在人才上拥有独特优势。 如何吸引人才&#…

二叉树OJ题汇总

本专栏内容为&#xff1a;leetcode刷题专栏&#xff0c;记录了leetcode热门题目以及重难点题目的详细记录 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;Leetcode &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库&#x1f69a; &…

基于B样条的FFD自由变换原理与C++实现

原理&#xff1a; https://blog.csdn.net/shandianfengfan/article/details/113706496 https://blog.csdn.net/qq_38517015/article/details/99724916 https://blog.csdn.net/qq_38517015/article/details/99724916 三次B样条 cubicBSplineFFD .h #pragma once #include &quo…

行业追踪,2023-11-03

自动复盘 2023-11-03 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

QT5通过openss1.1.1实现RSA加密

QT5通过openss1.1.1实现RSA加密 1.查看当前版本的QT依赖哪个版本的openssl #include <QSslSocket> #include <QDebug>qDebug() << QSslSocket::sslLibraryBuildVersionString();控制台输出&#xff1a; "OpenSSL 1.1.1w 11 Sep 2023"可以看到大…

Sulfo-CY3 azide水溶型橘红色荧光染料-星戈瑞

Sulfo-CY3 azide通常被认为是一种红色至近红外光谱区的荧光染料&#xff0c;其发射波长约为560-580纳米左右&#xff0c;而不是橘黄色。它属于Cyanine家族的染料&#xff0c;与其他Cyanine染料一样&#xff0c;其颜色主要是由其分子结构和共轭体系所决定的。 Sulfo-CY3 azide是…

【软件测试】银行项目具体操作流程

项目背景&#xff1a; XXXX银行项目采用的是B/S架构&#xff0c;主要是为了解决银行业务中的柜员、凭证、现金、账务等来自存款、贷款、会计模块的管理。 手工弊端&#xff1a; 1.项目业务复杂度高&#xff0c;回归测试工作量大 2.单个接口功能比较简单&#xff0c;容易实现自…