目录
三、@Import(AutoConfigurationImportSelector.class)
1)、AutoConfigurationImportSelector介绍
2-1)、ConfigurationClassParser#parse()
2-2)、this.deferredImportSelectorHandler.process()
2-2-2)、第二步:processGroupImports()
本篇源码基于spring-boot-2.1.0.RELEASE版本进行分析,各个版本可能存在一些差别。
SpringBoot的启动从main()方法开始:
@SpringBootApplication
public class SampleTomcatApplication {
public static void main(String[] args) {
SpringApplication.run(SampleTomcatApplication.class, args);
}
}
在主启动类上,我们只需要加上@SpringBootApplication注解,就能成功启动springboot应用了,那么我们就从注解入手,看下它是如何帮我们配置好所需要的一些环境的。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// @SpringBootConfiguration内部使用@Configuration修饰,标记启动类是一个Spring的配置类
@SpringBootConfiguration
// @EnableAutoConfiguration: 实现自动装配的核心注解. 内部包含两个注解:@AutoConfigurationPackage + @Import(AutoConfigurationImportSelector.class)
@EnableAutoConfiguration
// 包扫描:扫描启动类所在的包以及子包所有Bean组件并注册到IOC容器中
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
// SpringBoot项目启动的核心注解
// @SpringBootApplication = @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan
public @interface SpringBootApplication {
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
// 等同于EnableAutoConfiguration注解的exclude属性
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
// 等同于EnableAutoConfiguration注解的excludeName属性
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
/**
* Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
* for a type-safe alternative to String-based package names.
* @return base packages to scan
* @since 1.3.0
*/
// 等同于ComponentScan注解的basePackages属性
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
/**
* Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
* @return base packages to scan
* @since 1.3.0
*/
// 等同于ComponentScan注解的basePackageClasses属性
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
@SpringBootApplication 使用三个注解修饰:
标记启动类是一个Spring的配置类。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
// 相当于@SpringBootConfiguration就是@Configuration
public @interface SpringBootConfiguration {
}
扫描启动类所在的包以及子包所有Bean组件并注册到IOC容器中。
实现自动装配的核心注解,内部包含两个注解:@AutoConfigurationPackage + @Import(AutoConfigurationImportSelector.class)。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 自动配置包
@AutoConfigurationPackage
// 使用@Import注解导入AutoConfigurationImportSelector类,实现了ImportSelector接口,重写了selectImports()方法,帮助我们返回所有需要被注册为bean的类全限定类名的数组集合
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* 排除特定的自动配置类,使它们永远不会被应用
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
/**
* 排除特定的自动配置类名称,使其永远不会被应用
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
springboot的自动装配就是依靠@EnableAutoConfiguration这个注解实现的,而@EnableAutoConfiguration又是由下面两部分组成:
1、@AutoConfigurationPackage
2、@Import(AutoConfigurationImportSelector.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 向容器中导入了AutoConfigurationPackages.Registrar组件
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
可以看到,@AutoConfigurationPackage注解的功能是由@Import注解实现的,它是Spring框架的底层注解,它的作用就是给容器中导入某个组件类AutoConfigurationPackages.Registrar,我们看下这是个什么组件?
@AutoConfigurationPackage注解的作用是将添加该注解的类所在的package作为自动配置package进行管理。也就是说当SpringBoot应用启动时默认会将启动类所在的package作为自动配置的package,这样主程序类所在包及所有子包下的组件就会被扫描到spring容器中。
// 实现了ImportBeanDefinitionRegistrar、DeterminableImports接口,重写了registerBeanDefinitions()、determineImports()方法
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
// 注册bean定义
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}
// 返回一组需要注入的对象
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}
}
AutoConfigurationPackages.Registrar实现了ImportBeanDefinitionRegistrar,并重写了registerBeanDefinitions()注册bean定义信息的方法,这样在刷新IOC容器,即执行refresh()方法,在invokeBeanFactoryPostProcessors(beanFactory)这一步,会执行BeanDefinitionRegistryPostProcessor这个后置处理器的增强方法时,执行loadBeanDefinitions()加载bean定义信息,就会扫描到AutoConfigurationPackages.Registrar这个类,然后就会回调AutoConfigurationPackages.Registrar中的registerBeanDefinitions()方法:
register(registry, new PackageImport(metadata).getPackageName());
// 注册自动包规则的bean定义信息
public static void register(BeanDefinitionRegistry registry, String... packageNames) { // sample.tomcat
// 判断bean定义信息注册表中是否存在org.springframework.boot.autoconfigure.AutoConfigurationPackages
if (registry.containsBeanDefinition(BEAN)) {
// 如果该bean已经注册,则将要注册包名称添加进去
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition
.getConstructorArgumentValues();
constructorArguments.addIndexedArgumentValue(0,
addBasePackages(constructorArguments, packageNames));
}
else { // 如果该bean尚未注册,则注册该bean,参数中提供的包名称会被设置到bean定义中去
// 创建简单类型的bean定义信息GenericBeanDefinition
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
// bean类 : AutoConfigurationPackages$BasePackages
beanDefinition.setBeanClass(BasePackages.class);
// bean实例构造函数第一个参数 : 被使用类所在包名 (比如@SpringBootApplication注解所在类的包名)
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
packageNames);
// bean 角色 : 基础设施
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// 注册bean定义信息, bean名称 : org.springframework.boot.autoconfigure.AutoConfigurationPackages
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}
在本例中,SampleTomcatApplication在sample.tomcat包中,所以自动配置package为sample.tomcat。


@AutoConfigurationPackage和@ComponentScan的作用有什么区别?
以上面的示例来说,其实@AutoConfigurationPackage就相当于在启动类加上@ComponentScan(basePackages = "sample.tomcat")。
@EnableAutoConfiguration注解通过@Import(AutoConfigurationImportSelector.class)向容器中导入了AutoConfigurationImportSelector组件,它实现了DeferredImportSelector,并间接实现了ImportSelector接口,重写了selectImports()方法,帮助我们返回所有需要被注册为bean的类全限定类名的数组集合。
public class AutoConfigurationImportSelector
implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware,
BeanFactoryAware, EnvironmentAware, Ordered {
//...
}
我们先来看下AutoConfigurationImportSelector的类图:

AutoConfigurationImportSelector实现的是DeferredImportSelector接口,而不是ImportSelector接口,两者的执行时间是不一样的:
前面介绍到,AutoConfigurationImportSelector实现的是DeferredImportSelector接口,那么下面我们来看看它在哪里发挥作用的。
回到启动类的main()方法:
SpringApplication.run(SampleTomcatApplication.class, args);
执行run()方法:
// 运行 Spring 应用程序,创建并刷新一个新的ApplicationContext
public ConfigurableApplicationContext run(String... args) {
// 创建一个任务执行观察器,用于统计run启动过程花了多少时间
StopWatch stopWatch = new StopWatch();
// 记录开始时间
stopWatch.start();
ConfigurableApplicationContext context = null;
// exceptionReporters集合用来存储异常报告器,用来报告SpringBoot启动过程的异常
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 设置了一个名为java.awt.headless的系统属性, 其实是想设置该应用程序,即使没有检测到显示器,也允许其启动. 对于服务器来说,是不需要显示器的,所以要这样设置.
configureHeadlessProperty();
// 从spring.factories配置文件中加载到EventPublishingRunListener对象并赋值给SpringApplicationRunListeners
// EventPublishingRunListener对象主要用来发布SpringBoot启动过程中内置的一些生命周期事件,标志每个不同启动阶段
SpringApplicationRunListeners listeners = getRunListeners(args);
// 发布启动事件
listeners.starting();
try {
// 创建ApplicationArguments对象,封装了args参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 准备环境,包括系统变量、环境变量、命令行参数、默认变量等
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 配置需要忽略的BeanInfo信息
configureIgnoreBeanInfo(environment);
// 启动时控制台打印Banner
Banner printedBanner = printBanner(environment);
// 根据不同类型创建不同类型的spring容器ApplicationContext应用程序上下文
context = createApplicationContext();
// 加载spring.factories配置文件配置的异常报告器
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 准备上下文,刷新容器前的一些操作
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新应用上下文。完成Spring IOC容器的初始化
refreshContext(context);
// 在刷新上下文后调用的钩子,这个方法是一个模板方法
afterRefresh(context, applicationArguments);
// 停止记录执行时间
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// 事件广播,启动完成了
listeners.started(context);
// 执行ApplicationRunner、CommandLineRunner的run方法,实现spring容器启动成功后需要执行的一些逻辑
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;
}
上述代码是springboot应用启动的核心流程,我们重点关注refreshContext(context),也就是刷新Spring的IOC容器方法,自动配置就是在里面实现的。
refreshContext()方法实际上是调用org.springframework.context.support.AbstractApplicationContext#refresh,这个方法是IOC容器刷新的核心方法,主要包括12个步骤:
重点关注上述的第五步:invokeBeanFactoryPostProcessors(beanFactory):执行BeanFactoryPostProcessor的postProcessBeanFactory()增强方法;这一步会执行invokeBeanDefinitionRegistryPostProcessors()方法,然后执行BeanDefinitionRegistryPostProcessor后置处理器的postProcessBeanDefinitionRegistry()增强方法。
其中就包含了ConfigurationClassPostProcessor的处理,加载bean定义信息、ConfigurationClassParser解析自动配置相关的类。
调用栈如下:
org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:232)
org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:275)
org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:95)
org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:691)
org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:528)
BeanDefinitionRegistryPostProcessor主要完成对@Configuration注解的处理,类图如下:

我们查看ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry方法的源码:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
int registryId = System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
}
if (this.factoriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + registry);
}
this.registriesPostProcessed.add(registryId);
processConfigBeanDefinitions(registry);
}
在processConfigBeanDefinitions(registry)中通过ConfigurationClassParser#parse()方法解析bean定义信息:

查看ConfigurationClassParser#parse()方法:

parse()方法主要包括两个步骤:
主要是调用ConfigurationClassParser#processConfigurationClass来完成解析配置类:
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName));
}
真正解析配置类是在ConfigurationClassParser#doProcessConfigurationClass()方法,这里涉及解析很多自动配置相关的注解,如@PropertySource、@ComponentScan、@Import、@ImportResource等。
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass);
}
// 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), 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;
}

自动配置注解的@Import就是在这里解析的。查看ConfigurationClassParser#processImports(),看下具体是如何解析@Import注解的:

查看源码可知,如果@Import导入的是DeferredImportSelector类型,则会将selector先保存到deferredImportSelectorHandler中,并不会马上执行ImportSelector的selectImports()方法,等到所有的注解都解析完成后,再执行;而非DeferredImportSelector类型,则直接调用ImportSelector的selectImports()方法,然后注入对应的Bean对象;
org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)方法解析完注解bean定义信息后,才会执行延迟ImportSelector的bean导入,即调用this.deferredImportSelectorHandler.process()。
这里也印证了AutoConfigurationImportSelector实现的DeferredImportSelector的执行时机,是在@Configguration注解中的其他逻辑被处理完毕之后,包括对@ImportResource、@Bean这些注解的处理。
回到org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)方法的最后一行:
this.deferredImportSelectorHandler.process();
public void process() {
// 获取到上一步暂存在deferredImportSelectors中的ImportSelector
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
try {
if (deferredImports != null) {
// 创建延迟ImportSelector的分组处理器,内部是分组的
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
// 排序
deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
// 第一步:register()
deferredImports.forEach(handler::register);
// 第二步:processGroupImports()
handler.processGroupImports();
}
}
finally {
this.deferredImportSelectors = new ArrayList<>();
}
}
下面详细分析两个重要步骤。

register()方法主要作用是注册分组,DeferredImportSelectorGroupingHandler根据Group来分组,用于区分不同的ImportSelector。

看下grouping.getImports()。ConfigurationClassParser.DeferredImportSelectorGrouping#getImports():
public Iterable<Group.Entry> getImports() {
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
return this.group.selectImports();
}

getImports()处理包括两步:
2-2-2-1)、process()方法
AutoConfigurationImportSelector内部定义了AutoConfigurationGroup,它实现了DeferredImportSelector.Group接口,并重写了process()和selectImports()方法。
所以,这里就会执行的是重写后的AutoConfigurationImportSelector.AutoConfigurationGroup#process()方法:
// 这里用来处理自动配置类,比如过滤掉不符合匹配条件的自动配置类
@Override
public void process(AnnotationMetadata annotationMetadata,
DeferredImportSelector deferredImportSelector) {
Assert.state(
deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
// 自动装配的方法入口
// 调用getAutoConfigurationEntry()方法得到自动配置类并封装成autoConfigurationEntry对象
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(),
annotationMetadata);
// 将封装了自动配置类的autoConfigurationEntry对象装进autoConfigurationEntries集合
this.autoConfigurationEntries.add(autoConfigurationEntry);
// 遍历所有自动配置类
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
// 将符合条件的自动配置类存入到map中:Map<String, AnnotationMetadata> entries
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}

AutoConfigurationImportSelector#getAutoConfigurationEntry()方法,获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费。
// 获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
// 再次判断是否开启自动配置功能,判断是否有配置spring.boot.enableautoconfiguration属性,默认为true。
if (!isEnabled(annotationMetadata)) {
// 未开启自动配置,直接返回{}
return EMPTY_ENTRY;
}
// 获取@EnableAutoConfiguration注解的 exclude 和 excludeName 属性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取META-INF/spring.factories文件配置的所有自动配置类,实际上是通过SpringFactoriesLoader.loadFactoryNames()获取
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 删除重复的自动配置类,实际上就是将List包装成LinkedHashSet去重
configurations = removeDuplicates(configurations);
// 获取到需要排除自动装配的类、类名称,以及配置文件中(属性名:spring.autoconfigure.exclude)配置的类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 检查configurations中哪些是需要排除的
checkExcludedClasses(configurations, exclusions);
// 真正从候选自动配置类集合中移除掉需要排除的那些配置类
configurations.removeAll(exclusions);
// 执行AutoConfigurationImportFilter过滤器match方法判断是否符合@ConditionalOnBean,@ConditionalOnClass或@ConditionalOnWebApplication,循环所有的候选的自动配置组件,判断有没有哪些组件不符合条件的,将它们排除掉
configurations = filter(configurations, autoConfigurationMetadata);
// 获取了符合条件的自动配置类后,触发AutoConfigurationImportEvent事件,目的是告诉ConditionEvaluationReport条件评估报告器对象来记录符合条件的自动配置类
fireAutoConfigurationImportEvents(configurations, exclusions);
// 最终的返回的结果,封装成AutoConfigurationEntry
return new AutoConfigurationEntry(configurations, exclusions);
}

AutoConfigurationImportSelector#getCandidateConfigurations 获取候选的自动配置类。

实际上是通过类加载器加载"META-INF/spring.factories"文件中EnableAutoConfiguration对应的自动配置类全类名。

public Iterable<Entry> selectImports() {
if (this.autoConfigurationEntries.isEmpty()) {
return Collections.emptyList();
}
// 获取所有要排除的自动配置类
Set<String> allExclusions = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getExclusions)
.flatMap(Collection::stream).collect(Collectors.toSet());
// 获取经过滤后所有符合条件的自动配置类
Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getConfigurations)
.flatMap(Collection::stream)
.collect(Collectors.toCollection(LinkedHashSet::new));
// 删除需要排除的那些自动配置类
processedConfigurations.removeAll(allExclusions);
// 自动配置类排序
return sortAutoConfigurations(processedConfigurations,
getAutoConfigurationMetadata())
.stream()
.map((importClassName) -> new Entry(
this.entries.get(importClassName), importClassName))
.collect(Collectors.toList());
}
自动配置的类全类名拿到后,执行selectImports()。
2-2-2-2)、selectImports()方法
获取所有要排除的自动配置类,然后删除不符合条件的那些自动配置类,最后做了一下排序。
至此,getImports()方法执行结束,然后遍历所有获取到的自动配置类的全限定类名,挨个调用processImports()方法注入bean。
我们回到org.springframework.context.annotation.ConfigurationClassParser#processImports()方法:

调用addImportBeanDefinitionRegistrar()方法将bean定义注册到importBeanDefinitionRegistrars中,最后通过ImportBeanDefinitionRegistrar#registerBeanDefinitions()方法注册对应的bean。
我们看org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions的如下代码,parse()方法前面已经分析完成了,获取到了自动配置的bean的定义信息,接下来会执行:this.reader.loadBeanDefinitions(configClasses);加载bean定义信息,注册bean到spring容器中。


到这里,SpringBoot的自动配置原理就分析完了。
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
我有一个在Linux服务器上运行的ruby脚本。它不使用rails或任何东西。它基本上是一个命令行ruby脚本,可以像这样传递参数:./ruby_script.rbarg1arg2如何将参数抽象到配置文件(例如yaml文件或其他文件)中?您能否举例说明如何做到这一点?提前谢谢你。 最佳答案 首先,您可以运行一个写入YAML配置文件的独立脚本:require"yaml"File.write("path_to_yaml_file",[arg1,arg2].to_yaml)然后,在您的应用中阅读它:require"yaml"arg
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm
一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame
之前在培训新生的时候,windows环境下配置opencv环境一直教的都是网上主流的vsstudio配置属性表,但是这个似乎对新生来说难度略高(虽然个人觉得完全是他们自己的问题),加之暑假之后对cmake实在是爱不释手,且这样配置确实十分简单(其实都不需要配置),故斗胆妄言vscode下配置CV之法。其实极为简单,图比较多所以很长。如果你看此文还配不好,你应该思考一下是不是自己的问题。闲话少说,直接开始。0.CMkae简介有的人到大二了都不知道cmake是什么,我不说是谁。CMake是一个开源免费并且跨平台的构建工具,可以用简单的语句来描述所有平台的编译过程。它能够根据当前所在平台输出对应的m
我们目前正在为ROR3.2开发自定义cms引擎。在这个过程中,我们希望成为我们的rails应用程序中的一等公民的几个类类型起源,这意味着它们应该驻留在应用程序的app文件夹下,它是插件。目前我们有以下类型:数据源数据类型查看我在app文件夹下创建了多个目录来保存这些:应用/数据源应用/数据类型应用/View更多类型将随之而来,我有点担心应用程序文件夹被这么多目录污染。因此,我想将它们移动到一个子目录/模块中,该子目录/模块包含cms定义的所有类型。所有类都应位于MyCms命名空间内,目录布局应如下所示:应用程序/my_cms/data_source应用程序/my_cms/data_ty
注意:本文主要掌握DCN自研无线产品的基本配置方法和注意事项,能够进行一般的项目实施、调试与运维AP基本配置命令AP登录用户名和密码均为:adminAP默认IP地址为:192.168.1.10AP默认情况下DHCP开启AP静态地址配置:setmanagementstatic-ip192.168.10.1AP开启/关闭DHCP功能:setmanagementdhcp-statusup/downAP设置默认网关:setstatic-ip-routegeteway192.168.10.254查看AP基本信息:getsystemgetmanagementgetmanaged-apgetrouteAP配
1.1.1 YARN的介绍 为克服Hadoop1.0中HDFS和MapReduce存在的各种问题⽽提出的,针对Hadoop1.0中的MapReduce在扩展性和多框架⽀持⽅⾯的不⾜,提出了全新的资源管理框架YARN. ApacheYARN(YetanotherResourceNegotiator的缩写)是Hadoop集群的资源管理系统,负责为计算程序提供服务器计算资源,相当于⼀个分布式的操作系统平台,⽽MapReduce等计算程序则相当于运⾏于操作系统之上的应⽤程序。 YARN被引⼊Hadoop2,最初是为了改善MapReduce的实现,但是因为具有⾜够的通⽤性,同样可以⽀持其他的分布式计算模
我是ruby的新手,正在配置IRB。我喜欢pretty-print(需要'pp'),但总是输入pp来漂亮地打印它似乎很麻烦。我想做的是默认情况下让它漂亮地打印出来,所以如果我有一个var,比如说,'myvar',然后键入myvar,它会自动调用pretty_inspect而不是常规检查。我从哪里开始?理想情况下,我将能够向我的.irbrc文件添加一个自动调用的方法。有什么想法吗?谢谢! 最佳答案 irb中默认pretty-print对象正是hirb被迫去做。Theseposts解释hirb如何将几乎所有内容转换为ascii表。虽