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 first
        processMemberClasses(configClass, sourceClass, filter);
    }

    // Process any @PropertySource annotations
    for (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 annotations
    Set<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 immediately
            Set<BeanDefinitionHolder> scannedBeanDefinitions =
                this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
            // Check the set of scanned definitions for any further config classes and parse recursively if needed
            for (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 annotations
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

    // Process any @ImportResource annotations
    AnnotationAttributes 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 methods
    Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    for (MethodMetadata methodMetadata : beanMethods) {
        configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    }

    // Process default methods on interfaces
    processInterfaces(configClass, sourceClass);

    // Process superclass, if any
    if (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 recurse
            return sourceClass.getSuperClass();
        }
    }

    // No superclass -> processing is complete
    return 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 imports
						Class<?> 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 definitions
						Class<?> 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 class
						this.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{
    
	@Override
	public 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);
    
	@Override
	public 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 {
        
    	@Override
    	public 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.mfbz.cn/a/482946.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Uibot6.0 (RPA财务机器人师资培训第3天 )财务招聘信息抓取机器人案例实战

训练网站&#xff1a;泓江科技 (lessonplan.cn)https://laiye.lessonplan.cn/list/ec0f5080-e1de-11ee-a1d8-3f479df4d981https://laiye.lessonplan.cn/list/ec0f5080-e1de-11ee-a1d8-3f479df4d981https://laiye.lessonplan.cn/list/ec0f5080-e1de-11ee-a1d8-3f479df4d981(本博…

使用 VMWare 安装 Android-x86 系统(小白版)

文章目录 VMWare 介绍Android 系统介绍概述最终效果前置步骤开始安装 VMWare 介绍 VMware Workstation是VMware公司开发的一款桌面虚拟化软件。它允许用户在一台物理计算机上同时运行多个操作系统&#xff0c;每个操作系统都在自己的虚拟机中运行。这使得用户可以在同一台计算…

数据可视化-ECharts Html项目实战(5)

在之前的文章中&#xff0c;我们学习了如何设置滚动图例&#xff0c;工具箱设置和插入图片。想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&#xff0c;请留下你宝贵的点赞&#xff0c;谢谢 数据可视化-ECharts…

计算机基础系列 —— 从 Nand 门、DFF 到 RAM

Memory: The faculty of the brain by which data or information is encoded, stored, and retrieved when needed.It is the retention of information over time for the purpose of influencing future action —— Wikipedia 文中提到的所有实现都可以参考&#xff1a;nan…

dubbo 源码系列之-集群三板斧---负载均衡(二)

在上一课时我们了解了 LoadBalance 接口定义以及 AbstractLoadBalance 抽象类的内容&#xff0c;还详细介绍了 ConsistentHashLoadBalance 以及 RandomLoadBalance 这两个实现类的核心原理和大致实现。本课时我们将继续介绍 LoadBalance 的剩余三个实现。 LeastActiveLoadBala…

使用 Flink + Faker Connector 生成测试数据压测 MySQL

博主历时三年精心创作的《大数据平台架构与原型实现&#xff1a;数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行&#xff0c;点击《重磅推荐&#xff1a;建大数据平台太难了&#xff01;给我发个工程原型吧&#xff01;》了解图书详情&#xff0c;…

【数据结构】顺序表习题之移除元素和合并两个有效数组

&#x1f451;个人主页&#xff1a;啊Q闻 &#x1f387;收录专栏&#xff1a;《数据结构》 &#x1f389;道阻且长&#xff0c;行则将至 前言 嗨呀&#xff0c;今天的博客是关于顺序表的两道题目&#xff0c;是力扣的移除元素和合并有序数组的题目。 一.移除…

基于springboot和vue的旅游资源网站的设计与实现

环境以及简介 基于vue, springboot旅游资源网站的设计与实现&#xff0c;Java项目&#xff0c;SpringBoot项目&#xff0c;含开发文档&#xff0c;源码&#xff0c;数据库以及ppt 环境配置&#xff1a; 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xf…

力扣题库88题:合并两个有序数组(c语言)

解法&#xff1a; void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n) {int l1m-1;int l2n-1;int l3mn-1;while(l1>0&&l2>0){if(nums1[l1]>nums2[l2]){nums1[l3--]nums1[l1--];}else{nums1[l3--]nums2[l2--];}}while(l2>0)…

LinuxYUMVimg++/gccgdbGit使用

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

【C++算法】二分算法、二分模板详解,四道例题带详细注释

文章目录 [toc]1&#xff09;整数二分2&#xff09;解二分题步骤AcWing 789.数的范围洛谷 P1873.EKO/砍树洛谷 P1678.烦恼的高考志愿 2&#xff09;浮点二分AcWing 790. 数的三次方根 1&#xff09;整数二分 有单调性的题目一定可以二分&#xff0c;但是用二分做的题目不一定拥…

【物联网开源平台】tingsboard二次开发环境搭建+编译

文章目录 一&#xff0c;需要准备的环境二&#xff0c;获取tingsboard源码1.git拉取源码2.下载源码压缩包 三.新建仓库存放依赖文件四&#xff0c;编译五&#xff0c;遇到的错误 提示&#xff1a; 1.这篇只要准备两个环境&#xff0c;方法更简单&#xff01; 2.基于tingsboard …

动态路由协议——OSPF

目录 一.OSPF来源 二.OSPF术语 1.area id——区域的划分 2.cost——路径开销值 3.route id 4.LSDB表 5.邻居表 6.OSPF路由表 三.OSPF工作过程 1.交互hello报文建立邻居关系 2.选举主从 3.交互LSDB摘要信息 4.LSR,LSU,LSACK同步LSDB表项 5.各自计算路由 四.OSPF交…

【Linux命令】查看内存占用情况(mem, swap)

1. 方法1&#xff08;top&#xff09; # top2.方法2&#xff08;free&#xff09; # free -h3. 方法3&#xff08;swapon&#xff09; # swapon -s

Spring Boot1

SpringBoot概述 Spring Boot是Spring提供的一个子项目&#xff0c;用于快速构建Spring应用程序 SpringBoot特性 起步依赖 本质上就是一个Maven坐标&#xff0c;整合了完成一个功能所需要的所有坐标 自动配置 遵循约定大于配置的原则&#xff0c;再boot程序启动后&#xff0…

【MySQL】深入解析事务与MVCC

文章目录 1、事务四大特性1.1、原子性1.2、一致性1.3、隔离性1.4、持久性 2、并发事务带来问题2.1、脏读2.2、不可重复读2.3、幻读 3、事务隔离级别3.1、读未提交3.2、读已提交3.3、可重复读3.4、串行化 4、MVCC4.1、InnoDB隐藏字段4.2、undo log版本链4.3、ReadView4.4、MVCC工…

fiddler过滤器使用,隐藏图片、js、css请求

如果抓包过程中不想查看图片、js、css请求&#xff0c;或者只想抓某个ip或者某个网页下的请求&#xff0c;可以在过滤器中设置。 &#xff08;1&#xff09;没有开启过滤器 可以看出所有的请求都会抓取&#xff0c;cs、js、图片请求都有 &#xff08;2&#xff09;开启过滤器 …

波奇学Linux:网络套接字

domain:ipv4 还是ipv6 type:面向字节流还是... 虚拟机 云服务器禁止直接bind公网ip 服务器可以有多个ip&#xff0c;如果只绑定一个ip&#xff0c;只能收到来自一个ip的信息 任意地址绑定 关于port的问题 [0,1024]&#xff1a;系统内定的端口号&#xff0c;一般要用固定的应…

基于SpringBoot+MyBatis框架的智慧生活商城系统的设计与实现(源码+LW+部署+讲解)

目录 前言 需求分析 可行性分析 技术实现 后端框架&#xff1a;Spring Boot 持久层框架&#xff1a;MyBatis 前端框架&#xff1a;Vue.js 数据库&#xff1a;MySQL 功能介绍 前台功能拓展 商品详情单管理 个人中心 秒杀活动 推荐系统 评论与评分系统 后台功能拓…

基于Matlab的眼底图像血管分割,Matlab实现

博主简介&#xff1a; 专注、专一于Matlab图像处理学习、交流&#xff0c;matlab图像代码代做/项目合作可以联系&#xff08;QQ:3249726188&#xff09; 个人主页&#xff1a;Matlab_ImagePro-CSDN博客 原则&#xff1a;代码均由本人编写完成&#xff0c;非中介&#xff0c;提供…