Spring Bean加载优先级

当我们使用 @ConditionalOnMissingBean / @ConditionalOnBean注解去给某个 bean 注入赋予条件时,那在条件判断时我们需要确保条件判断过程所需的环境已准备好。

举个例子

下面的代码中有两个配置类,涉及两个 Bean 的注入

配置类 ConfigA 需要注入一个 A,注入 A 没有条件,直接注入

class ConfigA {@Bean(name = "a")public A a() {return new A();}}

配置类 ConfigB 需要注入一个 B,注入 B 有条件,只有当容器中没有 A 时才符合注入条件并进行注入

class ConfigB {@Bean@ConditionalOnMissingBean(name = "a")public B b() {return new B();}}

这时候就会产生一个疑问?

注入 B 的时候,Spring 是如何知道容器中是否已经存在 A 了。。。

结论

如果 ConfigA 和 ConfigB 都是用 @Configuration 标注的普通配置类,那注入 B 的条件判断 @ConditionalOnMissingBean 结果是不确定的。

原理分析

Spring 配置类是有加载顺序的,Spring是根据加载顺序去判断容器中是否存在指定Bean

总的时间线

总的时间线共分为三部分

  1. 加载所有的@Component类,自然包括(@Service、@Controller、@Configuration…),并有序保存
  2. 按照上述保存的有序集合,遍历每一个@Component类中的每一个method,识别@Bean,注册 BeanDefinition
  3. 遍历所有的BeanDefinition,进行实例化

从上述时间线可以看到,解析 @ConditionalOnMissingBean 注解发生在时间线的第二部分。

在扫描到 @ConditionalOnMissingBean 注解时,会判断当前条件是否满足,满足则对当前 Bean 进行注册,而判断满足的条件就是根据注册保存的 BeanDefinition 集合去判断的。

因此,如果当前条件所依赖的 Bean 已经被扫描过了,并保存到了 BeanDefinition 集合中,那 Spring 就能正确的判断到 Bean 的存在。若当前条件所依赖的 Bean 还没有被扫描到,那 BeanDefinition 集合中就找不到,就会产生误判的情况,造成程序错误。

所以,@ConditionalOnMissingBean 要想正确被判断,我们就需要保证类的顺序性,保证当前依赖的 Bean 所在的类必须在本类之前被加载,而保证类的加载顺序,这就是第一部分做的事情。

时间线细粒度分析

1. 加载@Component类

上述分析中最重要的就是这部分,只要这部分的顺序性保证好了,那后续就不能出问题

这块代码的入口在 org.springframework.context.annotation.ConfigurationClassParser::parse

public void parse(Set<BeanDefinitionHolder> configCandidates) {for (BeanDefinitionHolder holder : configCandidates) {BeanDefinition bd = holder.getBeanDefinition();try {if (bd instanceof AnnotatedBeanDefinition) {parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());}else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());}else {parse(bd.getBeanClassName(), holder.getBeanName());}}catch (BeanDefinitionStoreException ex) {throw ex;}catch (Throwable ex) {throw new BeanDefinitionStoreException("Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);}}this.deferredImportSelectorHandler.process();
}

上述入口的逻辑可以看到,又分为了两部分,一部分是 parse,另一部分是 deferredImportSelectorHandler.process

步骤一:parse

下述代码就是 parse 做的事情,第一个进来的类是启动类。

3个重要的全局变量

  • configurationClasses,使用的是有序Map,这块保存的就是加载的@Component类,具有有序性,因此我们其实保证的就是这个集合中类的顺序,时间线第二部分就是有序遍历这个集合进行的。
  • deferredImportSelectorHandler,和入口的第二部分有关。
  • importStack,这是扫描的精髓,基于栈的形式递归当前入口
private final Map<ConfigurationClass, ConfigurationClass> configurationClasses = new LinkedHashMap<>();private final DeferredImportSelectorHandler deferredImportSelectorHandler = new DeferredImportSelectorHandler();private final ImportStack importStack = new ImportStack();protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)throws IOException {if (configClass.getMetadata().isAnnotated(Component.class.getName())) {// Recursively process any member (nested) classes firstprocessMemberClasses(configClass, sourceClass, filter);}// Process any @PropertySource annotationsfor (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class,org.springframework.context.annotation.PropertySource.class)) {if (this.environment instanceof ConfigurableEnvironment) {processPropertySource(propertySource);}else {logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +"]. Reason: Environment must implement ConfigurableEnvironment");}}// Process any @ComponentScan annotationsSet<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);if (!componentScans.isEmpty() &&!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {for (AnnotationAttributes componentScan : componentScans) {// The config class is annotated with @ComponentScan -> perform the scan immediatelySet<BeanDefinitionHolder> scannedBeanDefinitions =this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());// Check the set of scanned definitions for any further config classes and parse recursively if neededfor (BeanDefinitionHolder holder : scannedBeanDefinitions) {BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();if (bdCand == null) {bdCand = holder.getBeanDefinition();}if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {parse(bdCand.getBeanClassName(), holder.getBeanName());}}}}// Process any @Import annotationsprocessImports(configClass, sourceClass, getImports(sourceClass), filter, true);// Process any @ImportResource annotationsAnnotationAttributes importResource =AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);if (importResource != null) {String[] resources = importResource.getStringArray("locations");Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");for (String resource : resources) {String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);configClass.addImportedResource(resolvedResource, readerClass);}}// Process individual @Bean methodsSet<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);for (MethodMetadata methodMetadata : beanMethods) {configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));}// Process default methods on interfacesprocessInterfaces(configClass, sourceClass);// Process superclass, if anyif (sourceClass.getMetadata().hasSuperClass()) {String superclass = sourceClass.getMetadata().getSuperClassName();if (superclass != null && !superclass.startsWith("java") &&!this.knownSuperclasses.containsKey(superclass)) {this.knownSuperclasses.put(superclass, configClass);// Superclass found, return its annotation metadata and recursereturn sourceClass.getSuperClass();}}// No superclass -> processing is completereturn null;
}

上述逻辑比较多,简单概括做的事情

  1. 扫描自身、父类是否用注解@Component标注,对扫描到的类递归执行当前操作
  2. 扫描@PropertySource,并加载到Environment
  3. 扫描@ComponentScan,并扫描涉及到相关路径的所有类,对扫描到的类递归执行当前操作
  4. 扫描@Import,对扫描到的类递归执行当前操作
  5. 扫描@ImportResource,并加载到Environment

上述操作的每一步都会将扫描到的组件类添加到全局变量 configurationClasses 中。

由于当前操作基于递归实现,因此上述操作的有序性是得不到保证的。

这块核心的是步骤四,扫描@Import,下述代码是扫描@Import的逻辑

在看下述代码时可以先了解一下@Import的功能

@Import注解可以导入三种类

  • 普通类

  • 实现ImportSelector接口的类

    这个接口的主要作用是批量导入,实现接口 selectImports,可以看到返回值是一个数组,这里需要返回多个类的全限定类名,实现批量导入

    public interface ImportSelector {String[] selectImports(AnnotationMetadata importingClassMetadata);}
    
  • 实现ImportBeanDefinitionRegistrar接口的类

    这个接口的主要作用是提供 BeanDefinition 注册 Bean 的逻辑

    public interface ImportBeanDefinitionRegistrar {default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator) {registerBeanDefinitions(importingClassMetadata, registry);}}
    

了解了关于 @Import 的功能,就可以看下述代码了

	private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,boolean checkForCircularImports) {if (importCandidates.isEmpty()) {return;}if (checkForCircularImports && isChainedImportOnStack(configClass)) {this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));}else {this.importStack.push(configClass);try {for (SourceClass candidate : importCandidates) {if (candidate.isAssignable(ImportSelector.class)) {// Candidate class is an ImportSelector -> delegate to it to determine importsClass<?> candidateClass = candidate.loadClass();ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,this.environment, this.resourceLoader, this.registry);Predicate<String> selectorFilter = selector.getExclusionFilter();if (selectorFilter != null) {exclusionFilter = exclusionFilter.or(selectorFilter);}if (selector instanceof DeferredImportSelector) {this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);}else {String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);}}else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {// Candidate class is an ImportBeanDefinitionRegistrar ->// delegate to it to register additional bean definitionsClass<?> candidateClass = candidate.loadClass();ImportBeanDefinitionRegistrar registrar =ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,this.environment, this.resourceLoader, this.registry);configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());}else {// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->// process it as an @Configuration classthis.importStack.registerImport(currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);}}}catch (BeanDefinitionStoreException ex) {throw ex;}catch (Throwable ex) {throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" +configClass.getMetadata().getClassName() + "]", ex);}finally {this.importStack.pop();}}}

上述代码逻辑简单整理:

  1. 判断要导入的类是否实现ImportSelector接口

    • 判断是否实现DeferredImportSelector接口

      这里就比较核心了,如果实现了DeferredImportSelector接口,则将当前的Selector注册到全局变量deferredImportSelectorHandler中。先不进行扫描

    • 若不实现DeferredImportSelector接口,执行方法selectImports,将方法返回的数组中所有类都进行扫描

  2. 判断要导入的类是否实现ImportBeanDefinitionRegistrar接口

  3. 上述2个接口都没有实现,那就是导入的普通类,递归执行前面的扫描流程

步骤二:deferredImportSelectorHandler

通过步骤一可以得出,这里面保存的都是实现DeferredImportSelector接口的导入类。

这块的逻辑也是对deferredImportSelectorHandler中保存的类进行扫描处理。

那站在顺序性的角度考虑,这块逻辑中扫描的类的顺序肯定比步骤一的要晚。

可以得出结论,要想调整扫描顺序,让别的类在本类之前加载,那就可以让别的类在步骤一中加载,本类在步骤二中加载。

要想在步骤一中加载,需要用@Component修饰类

要想在步骤二中加载,需要一个实现DeferredImportSelector接口的导入类,将本类的全限定类名进行返回。

这时候自动装配类的作用就凸显出来了

Spring 若要开启自动装配,需要使用注解@EnableAutoConfiguration去修饰。

而注解@EnableAutoConfiguration又导入了一个AutoConfigurationImportSelector类,并且AutoConfigurationImportSelector类实现于DeferredImportSelector接口。

@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
public class AutoConfigurationImportSelector implements DeferredImportSelector{@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());}}

所有可以得出,自动装配类是在第二步骤中进行扫描的,自动装配类的加载顺序低于普通配置类

那要保证顺序,就可以让条件依赖的Bean使用普通配置类,本类使用自动装配类。

如果条件依赖的Bean也是自动装配类,那就可使用自动装配类的特性,进行自动装配类之前的顺序调整,比如(@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder)。

2. 扫描@Component类中每个method

这块逻辑的入口是 org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader::loadBeanDefinitions

class ConfigurationClassBeanDefinitionReader {/*** 入参就是时间线第一部分加载的组件类*/public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();for (ConfigurationClass configClass : configurationModel) {loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);}}private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {if (trackedConditionEvaluator.shouldSkip(configClass)) {String beanName = configClass.getBeanName();if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {this.registry.removeBeanDefinition(beanName);}this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());return;}if (configClass.isImported()) {registerBeanDefinitionForImportedConfigurationClass(configClass);}for (BeanMethod beanMethod : configClass.getBeanMethods()) {loadBeanDefinitionsForBeanMethod(beanMethod);}loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());}}

这块的逻辑主要有以下部分:

  1. 判断当前类是否需要跳过,依赖于类上的条件,比如我们经常在类上添加条件@ConditionalOnClass、@ConditionalOnProperty…
  2. 是否是导入类,将导入类本身注册为BeanDefinition
  3. 遍历每一个method,扫描判断每一个method所修饰的注解,比如@Bean、@Autowire、@Scope、@ConditionalOnMissingBean…并将当前Bean注册为BeanDefinition。

注册到BeanDefinition时保存的位置是BeanDefinitionRegistry的默认实现 DefaultListableBeanFactory中的全局变量beanDefinitionMap中,@Conditional条件判断就是判断这里面有没有目标Bean,若加载的比较晚,那肯定找不到,这也是场景中导致问题的根本原因。

public class DefaultListableBeanFactory extends BeanDefinitionRegistry {private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);private volatile List<String> beanDefinitionNames = new ArrayList<>(256);}

3. 实例化Bean

这块逻辑的位置在 org.springframework.beans.factory.support.DefaultListableBeanFactory::preInstantiateSingletons

public class DefaultListableBeanFactory extends BeanDefinitionRegistry {private volatile List<String> beanDefinitionNames = new ArrayList<>(256);@Overridepublic void preInstantiateSingletons() throws BeansException {if (logger.isTraceEnabled()) {logger.trace("Pre-instantiating singletons in " + this);}// Iterate over a copy to allow for init methods which in turn register new bean definitions.// While this may not be part of the regular factory bootstrap, it does otherwise work fine.List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);// Trigger initialization of all non-lazy singleton beans...for (String beanName : beanNames) {RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {if (isFactoryBean(beanName)) {Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);if (bean instanceof FactoryBean) {FactoryBean<?> factory = (FactoryBean<?>) bean;boolean isEagerInit;if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,getAccessControlContext());}else {isEagerInit = (factory instanceof SmartFactoryBean &&((SmartFactoryBean<?>) factory).isEagerInit());}if (isEagerInit) {getBean(beanName);}}}else {getBean(beanName);}}}// Trigger post-initialization callback for all applicable beans...for (String beanName : beanNames) {Object singletonInstance = getSingleton(beanName);if (singletonInstance instanceof SmartInitializingSingleton) {StartupStep smartInitialize = this.getApplicationStartup().start("spring.beans.smart-initialize").tag("beanName", beanName);SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;if (System.getSecurityManager() != null) {AccessController.doPrivileged((PrivilegedAction<Object>) () -> {smartSingleton.afterSingletonsInstantiated();return null;}, getAccessControlContext());}else {smartSingleton.afterSingletonsInstantiated();}smartInitialize.end();}}}}

这块主要做的事情是:

  1. 遍历每一个 beanDefinition,判断是否抽象、单例、懒加载,并对符合条件的 beanDefinition 进行实例化,完成 Bean 的注入。

    每个符合条件 beanDefinition 都会被调用一次 getBean 接口,找不到就会根据 beanDefinition 创建 Bean

  2. 为所有合适的 Bean 触发初始化后回调

总结

  • @ConditionalOnMissingBean 判断的时候,是按照注册BeanDefinition的顺序进行判断的,BeanDefinition的顺序是由类的加载顺序保证的。这个流程中所有Bean都还没有实例化。
  • 对于使用注解@ConditionalOnMissingBean、@ConditionalOnBean去对某个Bean的注入增加条件时,需要保证依赖的Bean是优先于当前类进行加载的。
  • 如何控制加载顺序?
    • 自动装配类的加载顺序是晚于普通配置类的
    • 自动装配类之前的加载顺序可以使用@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder进行保证。

请添加图片描述

常用注解的加载时机

  • @Component

    普通组件类,步骤一 parse 中加载

  • @Configuration

    功能和@Component一致,叫@Configuration只是为了起个别名,标志特定分类的组件,和@Controller、@Service一致

  • @Import(xxx.class)

    • 若导入的类没有实现 DeferredImportSelector 接口

      普通组件类,功能和@Component一致,唯一的区别就是导入的类可以不用加@Component去标注。

    • 若导入的类实现了 DeferredImportSelector 接口

      延迟加载,步骤二deferredImportSelectorHandler的时候加载,加载晚于普通组件类

      自动装配类就属于这种方式,但Spring给自动装配类增加了排序的特性,自动装配类之间可以排序。

  • @Enablexxx

    常见于通过@Enable开头的注解,用于某个功能的开启。

    这种实现本质上就是通过一个见名知意的注解去包装多个注解的功能,和把里面包装的注解写出来没区别,具体还是要看里面包装的注解做了什么事情

    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
    }
    
  • @AutoConfiguration

    无任何实质性的作用,只是把@Configuration、@AutoConfigureBefore、@AutoConfigureAfter这三个注解包装成一个注解,标识这是一个自动装配类,方便自动装配类之间的排序。

    但和上述@Enablexxx有个区别,Spring认这个注解,Spring在步骤一parse中对@ComponentScan的扫描阶段,发现此类如果用@AutoConfiguration标注,就会认为你是一个自动装配类,不进行扫描,跳过当前类

  • @AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder

    只生效于自动装配类,用于自动装配类之间的排序

自动装配类的配置

自动装配类有两种配置方式

  • resources/spring.factories

    key为EnableAutoConfiguration的全限定类名

    value为自动装配类的全限定类名

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.example.c.config.CConfig
    
  • resources/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

    里面一行一行的填自动装配类的全限定类名

    com.example.c.config.CConfig
    

    源码解析位置

    public class AutoConfigurationImportSelector implements DeferredImportSelector{protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {List<String> configurations = new ArrayList<>(SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);Assert.notEmpty(configurations,"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "+ "are using a custom packaging, make sure that file is correct.");return configurations;}}
    

常见问题

  1. 有一个类,既用@Configuration标注,又配置成了自动装配类,那他的解析时机发生在哪?

    Spring在步骤一parse中对@ComponentScan的扫描阶段,会经过一系列的过滤器,其中一个是自动配置类的过滤器

    这个过滤器的判断条件是,如果当前类用@Configuration标注,并且修饰的注解有@AutoConfiguration,或者配置成了自动装配类,那就跳过当前类的扫描

    因此,这种场景下,此类扫描的时机应是走自动装配类扫描的逻辑。

    public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware {@Overridepublic boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)throws IOException {return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);}private boolean isConfiguration(MetadataReader metadataReader) {return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());}private boolean isAutoConfiguration(MetadataReader metadataReader) {boolean annotatedWithAutoConfiguration = metadataReader.getAnnotationMetadata().isAnnotated(AutoConfiguration.class.getName());return annotatedWithAutoConfiguration|| getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());}protected List<String> getAutoConfigurations() {if (this.autoConfigurations == null) {List<String> autoConfigurations = new ArrayList<>(SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader));ImportCandidates.load(AutoConfiguration.class, this.beanClassLoader).forEach(autoConfigurations::add);this.autoConfigurations = autoConfigurations;}return this.autoConfigurations;}}
    
  2. 如果一个类用@AutoConfiguration注解修饰,但没有通过file文件的方式配置为自动装配类,那此类是什么时机被扫描的

    和上述逻辑一致,检测到使用@AutoConfiguration修饰,则在步骤一parse中会跳过当前类的扫描。

    但在步骤二中,又在自动装配类中没有找到当前类,则当前类不会被扫描。

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

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

相关文章

IDEA-JAVA 常用的的插件

文章目录 一、常用插件 一、常用插件 CodeGlance&#xff1a;在编辑器侧边栏显示代码缩略图。 Key Promoter X&#xff1a;在你使用快捷键时提示你相应的操作可以使用快捷键来完成。 BashSupport&#xff1a;提供 Bash 脚本语言的支持。 IdeaVim&#xff1a;将 Vim 的快捷键…

Unity与鼠标相关的事件(自己记忆用)

1. OnMouseDown&#xff1a;当用户按下鼠标按钮时调用。 - 参数&#xff1a;MouseEvent&#xff0c;可以用来确定哪个鼠标按钮被按下。 2. OnMouseUp&#xff1a;当用户释放鼠标按钮时调用。 - 参数&#xff1a;MouseEvent&#xff0c;可以用来确定哪个鼠标按钮被释放。…

SpringBoot源码探险 —— SpringBoot启动流程详解

一&#xff0c;SpringBoot启动流程 本人使用的SpringBootParent版本为 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.4.1</version><relativePath/>…

vsto worksheet中查找关键字【关键字】获取对应的整列 union成一个range

要在 VSTO 中的工作表中查找包含特定关键字的单元格&#xff0c;并将这些单元格所在列合并为一个范围&#xff0c;可以使用以下代码&#xff1a;csharp using Excel Microsoft.Office.Interop.Excel;// 在工作表中查找包含特定关键字的单元格&#xff0c;并返回这些单元格所在…

HTML世界之标签Ⅶ

目录 一、source 标签 二、span 标签 三、strong 标签 四、style 标签 五、sub 标签 六、summary 标签 七、sup 标签 八、textarea 标签 九、template 标签 十、time 标签 十一、title 标签 十二、track 标签 十三、video 标签 十四、wbr 标签 一、source 标签 …

计算机网络——26通用转发和SDN

通用转发和SDN 网络层功能&#xff1a; 转发&#xff1a; 对于从某个端口 到来的分组转发到合适的 输出端口路由&#xff1a; 决定分组从源端 到目标端的路径 网络层 传统路由器的功能 每个路由器(Per Route)的控制平面 &#xff08;传统&#xff09; 每个路由器上都有实…

Oracle数据库如果出现乱码,需要查看是否时字符集不一致导致乱码,这样解决

1、如果出现乱码&#xff0c;需要查看是否时字符集不一致导致乱码 以修改为ZHS16GBK字符集为例&#xff0c;具体字符集需要sql查询。 Oracle查看字符集 SELECT * FROM NLS_DATABASE_PARAMETERS p where p.PARAMETERNLS_CHARACTERSET; SELECT USERENV(language) FROM DUAL; 1.…

uni-app从零开始快速入门

教程介绍 跨端框架uni-app作为新起之秀&#xff0c;在不到两年的时间内&#xff0c;迅速被广大开发者青睐和推崇&#xff0c;得益于它颠覆性的优势“快”&#xff0c;快到可以节省7套代码。本课程由uni-app开发者团队成员亲授&#xff0c;带领大家无障碍快速掌握完整的uni-app…

STM32 CubeMx创建Lwip+FreeRtos时出现ping不通的问题

STM32 CubeMx创建LwipFreeRtos时出现ping不通 1、配置ETH&#xff0c;使用中断 2、配置Lwip&#xff08;使用静态ip&#xff09;&#xff0c;其余什么都不用管 3、配置FreeRtos&#xff08;选择V2版本&#xff09;&#xff0c;其余什么都不用管 4、创建代码 5、查看自动生…

目标检测预测框可视化python代码实现--OpenCV

import numpy as np import cv2 import colorsys from PIL import Image, ImageDraw, ImageFontdef puttext_cn(img, text, pt, color(255,0,0), size16):if (isinstance(img, np.ndarray)): # 判断是否OpenCV图片类型img Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2…

注册中心的基础知识

什么是注册中心 当服务启动时,将服务信息服务名称/IP/端口写入注册中心.注册中心接收服务端信息时保存服务信息,并且维护服务列表数据当服务消费者启动时会通过IP:端口(注册中心)远程链接注册中心. 获取服务列表信息.缓存到本地 当消费者调用服务时,查找缓存到本地的服务列表…

XSS一-WEB攻防-XSS跨站反射型存储型DOM型标签闭合输入输出JS代码解析

演示案例&#xff1a; XSS跨站-输入输出-原理&分类&闭合XSS跨站-分类测试-反射&存储&DOM #XSS跨站-输入输出-原理&分类&闭合 漏洞原理&#xff1a;接受输入数据&#xff0c;输出显示数据后解析执行 基础类型&#xff1a;反射(非持续)&#xff0c;存储(…

LinuxYUMVimg++/gccgdbGit使用

前言 大家好&#xff0c;我是jiantaoyab&#xff0c;前面的文章给大家介绍了Linux的基础命令和权限&#xff0c;学会了命令行的模式使用Linux&#xff0c;今后要开始在Linux上写代码了&#xff0c;在这篇文章将介绍YUM、vim、gdb、git等常用的工具。 先来看看Linux如何安装软…

怎么拆解台式电脑风扇CPU风扇的拆卸步骤-怎么挑

今天我就跟大家分享一下如何选购电脑风扇的知识。 我也会解释一下机箱散热风扇一般用多少转。 如果它恰好解决了您现在面临的问题&#xff0c;请不要忘记关注本站并立即开始&#xff01; 文章目录列表&#xff1a;大家一般机箱散热风扇都用多少转&#xff1f; 机箱散热风扇选择…

linux centos 安装jenkins,并构建spring boot项目

首先安装jenkins&#xff0c;使用war包安装&#xff0c;比较简单&#xff0c;注意看下载的版本需要的JDK版本&#xff0c;官网下载https://www.jenkins.io/download/ 把下载好的war包放到服务器上&#xff0c;然后运行&#xff0c;注意8080端口的放行 # 前台运行并指定端口 ja…

脉冲变压器电感的工艺结构原理及选型参数总结

🏡《总目录》 目录 1,概述2,工作原理3,结构特点3.1,铁心结构3.2,铁心材料3.3,绕组4,工艺流程4.1,准备铁芯4.2,绕制线圈4.3,安装线圈4.4,固定线圈4.5,绝缘处理4.6,高压脉冲引出

rtt的IO设备框架面向对象学习-oopc实现特点

网上oopc实现方式都能搜得到&#xff0c;如oopc参考文章&#xff0c;rtt的oopc也是基本一样。大家好像都有个共识了: &#xff08;1&#xff09;定义类都用struct——这一过程就是抽象封装的过程&#xff0c;把属性和方法封装到struct里面&#xff0c;方法用函数指针变量表示&a…

前端向后端传入json 后台怎么接收(params呢)

目录 一、使用POJO若前端传递过来的数据刚好和我们的bean实体对象属性一致&#xff0c;则可以使用对象的形式接收。后端实体类 二、使用Map接收后台Controller 三、使用RequestParams 1&#xff0c;params传参 2.地址拼接传参 当前端传来json数据时&#xff0c;后端有多种…

dji esdk开发(2)订阅实时视频流

文章目录 1、主要接口介绍1.1、订阅码流服务状态1.2、初始化、开始、和结束订阅码流1.2.1、订阅码流初始化1.2.2、开始码流传输1.2.3、停止码流传输1.3、设置负载相机码流源2、测试2.1、获取RGB图像2.1.1、解码部分2.1.2、完整测试代码如下2.2、目标检测并显示2.2.1、解码部分2…

数据结构 - 二叉树非递归遍历

文章目录 前言一、前序二、中序三、后序 前言 本文实现二叉树的前中后的非递归遍历&#xff0c;使用栈来模拟递归。 文字有点简略&#xff0c;需要看图和代码理解 树节点&#xff1a; typedef char DATA; //树节点 typedef struct Node {DATA data; //数据struct Node* left…