SpringBoot源码解读与原理分析(五)SpringBoot的装配机制

news2024/11/25 22:52:58

文章目录

    • 2.5 Spring Boot的装配机制
      • 2.5.1 @ComponentScan
        • 2.5.1.1 @ComponentScan的基本使用方法
        • 2.5.1.2 TypeExcludeFilter(类型排除过滤器)
        • 2.5.1.3 AutoConfigurationExcludeFilter(自动配置类排除过滤器)
      • 2.5.2 @SpringBootConfiguration
      • 2.5.3 @EnableAutoConfiguration
        • 2.5.3.1 javadoc描述
        • 2.5.3.2 @AutoConfigurationPackage
          • (1)Registrar
          • (2)PackageImports
          • (3)register方法
          • (4)BasePackages
          • (5)保存SpringBoot应用的根包路径作用
        • 2.5.3.3 AutoConfigurationImportSelector
          • (1)AutoConfigurationGroup
          • (2)getAutoConfigurationEntry
      • 2.5.4 总结

前面三小节分别介绍了Spring Framewoek的模块装配、条件装配和SPI机制。下面正式进入Spring Boot的装配机制。

2.5 Spring Boot的装配机制

实际上,Spring Boot的自动装配是模块装配+条件装配+SPI机制的组合使用,而这一切都凝聚在Spring Boot主启动类的@SpringBootApplication注解上。

@SpringBootApplication注解是由三个注解组合而成的复合注解:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { 
    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication

因此,主启动类标注@SpringBootApplication,会触发@EnableAutoConfiguration自动装配和@ComponentScan组件扫描。

2.5.1 @ComponentScan

@ComponentScan放在@SpringBootApplication中的意图:扫描主启动类所在包及其子包下的所有组件。
这也解释了为什么主启动类要放到所有类所在包的最外层。

2.5.1.1 @ComponentScan的基本使用方法

下面通过一个例子加以理解:
(1)创建Boss类和BossConfiguration类,两个类放在同一包下
两个类放在同一包下

@Component("aBoss")
public class Boss {
}
@Configuration
@ComponentScan
public class BossConfiguration {
}

通过@ComponentScan注解,SpringBoot会扫描BossConfiguration所在包及其子包的所有组件。由于Boss组件和BossConfiguration组件在同一包下,所以Boss组件将会组测到IOC容器中。

(2)测试

public class BossApp {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BossConfiguration.class);
        System.out.println("-------分割线-------");
        Stream.of(context.getBeanDefinitionNames()).forEach(System.out::println);
        System.out.println("-------分割线-------");
    }

}

输出结果(已省略一些内部组件打印,下同):

-------分割线-------
bossConfiguration
aBoss
-------分割线-------

(3)Boss类和BossConfiguration类放在同一个包下肯定是不合理的,所以调整一下位置,把BossConfiguration来放在其它目录下,如图:
两个类放在不同包下
此时的输出结果显示,Boss并没有注册到IOC容器:

-------分割线-------
bossConfiguration
-------分割线-------

(4)查看@ComponentScan的源码,可以找到几个常用的属性:

  • basePackages:定义扫描的包名,在没有定义的情况下,扫描当前包和其子包。
  • includeFilters:定义满足过滤器,只有满足过滤器条件的Bean才会注册。
  • excludeFilters:定义排除过滤器,满足过滤器条件的Bean会被排除注册。

在@ComponentScan加basePackages参数,指定Boss所在的包:

@Configuration
@ComponentScan(basePackages = "com.star.springboot.assemble.test01.pojo")
public class BossConfiguration {
}

此时的输出结果显示,Boss已经注册到IOC容器:

-------分割线-------
bossConfiguration
aBoss
-------分割线-------
2.5.1.2 TypeExcludeFilter(类型排除过滤器)

和平时使用不一样,@ComponentScan注解额外添加了两个过滤条件:

@ComponentScan(excludeFilters = { 
    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) 
})

它的作用是,让开发者向IOC容器注册自定义的过滤器组件,在组件扫描过程中匹配自定义过滤器的规则,以过滤掉一些不需要的组件。

javadoc:
Implementations should provide a subclass registered with {@link BeanFactory} and override the {@link #match(MetadataReader, MetadataReaderFactory)} method.
TypeExcludeFilter的使用方法:在BeanFactory中注册一个子类,并重写match方法,SpringBoot会自动找到这些子类并调用它们。

【拓展】由FilterType源码可知,@ComponentScan不仅支持自定义过滤器组件(CUSTOM),还支持以注解(ANNOTATION)、类(ASSIGNABLE_TYPE)、AspectJ(ASPECTJ)、正则(REGEX)为线索进行过滤。

public enum FilterType {

	/**
	 * Filter candidates marked with a given annotation.
	 * @see org.springframework.core.type.filter.AnnotationTypeFilter
	 */
	ANNOTATION,

	/**
	 * Filter candidates assignable to a given type.
	 * @see org.springframework.core.type.filter.AssignableTypeFilter
	 */
	ASSIGNABLE_TYPE,

	/**
	 * Filter candidates matching a given AspectJ type pattern expression.
	 * @see org.springframework.core.type.filter.AspectJTypeFilter
	 */
	ASPECTJ,

	/**
	 * Filter candidates matching a given regex pattern.
	 * @see org.springframework.core.type.filter.RegexPatternTypeFilter
	 */
	REGEX,

	/** Filter candidates using a given custom
	 * {@link org.springframework.core.type.filter.TypeFilter} implementation.
	 */
	CUSTOM

}

下面继续通过上面的例子加以理解:

(1)注册自定义的过滤器组件:在上下文注册TypeExcludeFilter的子类,并重写match方法,match方法返回true则匹配,返回false则不匹配。SpringBoot会自动找到这些TypeExcludeFilter的子类并执行match方法。

public class MyTypeExcludeFilter extends TypeExcludeFilter {

    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        System.out.println("自定义过滤器执行了...");
        return Boss.class.getName().equals(metadataReader.getClassMetadata().getClassName());
    }

}

(2)在@ComponentScan配置自定义过滤器组件

@Configuration
@ComponentScan(basePackages = "com.star.springboot.assemble.test01.pojo",
    excludeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM, classes = MyTypeExcludeFilter.class)})
public class BossConfiguration {

}

(3)测试

-------分割线-------
bossConfiguration
-------分割线-------

输出结果显示,Boss没有注册到IOC容器,说明自定义的过滤器组件生效了。

(4)TypeExcludeFilter的核心逻辑

TypeExcludeFilter的底层实现:从BeanFactory中提取出所有类为TypeExcludeFilter的Bean,并缓存到本地(空间换时间的设计体现),后续在进行组件扫描时会依次调用这些TypeExcludeFilter对象,检查被扫描的类是否符合匹配规则。

符合规则的,不注册到IOC容器;不符合的才注册到IOC容器。

public class TypeExcludeFilter implements TypeFilter, BeanFactoryAware {

    private BeanFactory beanFactory;

    private Collection<TypeExcludeFilter> delegates;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
        throws IOException {
        if (this.beanFactory instanceof ListableBeanFactory && getClass() == TypeExcludeFilter.class) {
            // 提取出所有TypeExcludeFilter并依次调用其match方法检查是否匹配
            for (TypeExcludeFilter delegate : getDelegates()) {
                if (delegate.match(metadataReader, metadataReaderFactory)) {
                    return true;
                }
            }
        }
        return false;
    }

    private Collection<TypeExcludeFilter> getDelegates() {
        Collection<TypeExcludeFilter> delegates = this.delegates;
        if (delegates == null) {
            // 从BeanFactory中提取所有类为TypeExcludeFilter的Bean
            delegates = ((ListableBeanFactory) this.beanFactory).getBeansOfType(TypeExcludeFilter.class).values();
            this.delegates = delegates;
        }
        return delegates;
    }

    // ...

}
2.5.1.3 AutoConfigurationExcludeFilter(自动配置类排除过滤器)
  • 被@Configuration注解标注的类是配置类。
  • 被spring.factories中的@EnableAutoConfiguration注解的配置类是自动配置类。
  • 作用:在组件扫描阶段过滤掉一些自动配置类。
  • AutoConfigurationExcludeFilter的源码:
public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware {

    private ClassLoader beanClassLoader;

    private volatile List<String> autoConfigurations;

    @Override
    public void setBeanClassLoader(ClassLoader beanClassLoader) {
        this.beanClassLoader = beanClassLoader;
    }

    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);
    }

    private boolean isConfiguration(MetadataReader metadataReader) {
        // 检查是配置类的规则:是否被@Configuration注解修饰
        return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());
    }

    private boolean isAutoConfiguration(MetadataReader metadataReader) {
        // 检查是自动配置类的规则:是否被定义在spring.factories中的EnableAutoConfiguration中
        return getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());
    }

    protected List<String> getAutoConfigurations() {
        if (this.autoConfigurations == null) {
            this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader);
        }
        return this.autoConfigurations;
    }

}

2.5.2 @SpringBootConfiguration

@SpringBootConfiguration其实很简单,本身仅组合了一个Spring Framework的@Configuration而已。换句话说,一个类标注了@SpringBootConfiguration,也就是标注了@Configuration,代表这个类是一个注解配置类。

@Configuration
public @interface SpringBootConfiguration

2.5.3 @EnableAutoConfiguration

@EnableAutoConfiguration是重头戏,承载了SpringBoot自动装配的灵魂。

2.5.3.1 javadoc描述

Enable auto-configuration of the Spring Application Context, attempting to guess and configure beans that you are likely to need. Auto-configuration classes are usually applied based on your classpath and what beans you have defined. For example, if you have tomcat-embedded.jar on your classpath you are likely to want a TomcatServletWebServerFactory (unless you have defined your own ServletWebServerFactory bean). When using @SpringBootApplication, the auto-configuration of the context is automatically enabled and adding this annotation has therefore no additional effect.

第一段讲解注解本身的作用。
标注@EnableAutoConfiguration后,Spring上下文的自动装配机制将会启用,SpringBoot会尝试猜测和配置当前项目可能需要的Bean。自动配置类的使用通常基于项目类路径和定义的Bean。例如,如果在类路径下引用了tomcat-embedded.jar包,则SpringBoot会猜测当前项目很可能需要配置一个TomcatServletWebServerFactory(除非项目已经手动配置了一个ServletWebServerFactory)。标注@SpringBootApplication后,Spring上下文的自动装配会自动启用,所以再配置@EnableAutoConfiguration没有附加作用(也就是不需要再配置@EnableAutoConfiguration)。

Auto-configuration tries to be as intelligent as possible and will back-away as you define more of your own configuration. You can always manually exclude() any configuration that you never want to apply (use excludeName() if you don’t have access to them). You can also exclude them via the spring.autoconfigure.exclude property. Auto-configuration is always applied after user-defined beans have been registered.

第二段讲解SpringBoot自动装配的机制和禁用方法。
SpringBoot自动装配会尽可能地智能化,并会在项目注册了更多自定义配置时自动退出(即被覆盖)。开发者可以手动排除任何不想使用的自动配置(使用exclude属性,或在无法访问时使用excludeName属性),还可以通过全局配置文件的spring.autoconfigure.exclude属性。自动配置总是在用户自定义的Bean被注册之后才进行。

The package of the class that is annotated with @EnableAutoConfiguration, usually via @SpringBootApplication, has specific significance and is often used as a ‘default’. For example, it will be used when scanning for @Entity classes. It is generally recommended that you place @EnableAutoConfiguration (if you’re not using @SpringBootApplication) in a root package so that all sub-packages and classes can be searched.

第三段讲解组件扫描的规则。
被@EnableAutoConfiguration或@SpringBootApplication标注的类,其所在包有特殊的含义,通常被定义为“默认值”。例如,当扫描被@Entity标注的类时会用到。通常建议,把@EnableAutoConfiguration标注在一个根包中的类,这个根包及其子包的所有组件都将被扫描。

Auto-configuration classes are regular Spring @Configuration beans. They are located using the SpringFactoriesLoader mechanism (keyed against this class). Generally auto-configuration beans are @Conditional beans (most often using @ConditionalOnClass and @ConditionalOnMissingBean annotations).

第四段讲解自动配置类与SPI机制的关系。
自动配置类本身也是常规的Spring配置类,只不过它们的加载是通过SpringFactoriesLoader机制(即SPI机制)。通常自动配置类也是条件配置类(被@Conditional系列注解标注,最常用的是@ConditionalOnClass和@ConditionalOnMissingBean注解)。

总结:标注@EnableAutoConfiguration后,会启用SpringBoot的自动装配,根据导入的依赖、上下文配置合理加载默认的自动配置。

@EnableAutoConfiguration本身也是一个组合注解,包含了两个注解:

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration
2.5.3.2 @AutoConfigurationPackage
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage

由以上源码可知,@AutoConfigurationPackage注解组合了一个@Import注解,导入了一个内部类AutoConfigurationPackages.Registrar。

@AutoConfigurationPackage的作用是,将主启动类所在的包记录下来,注册到AutoConfigurationPackages中。

在SpringBoot 2.3.0版本以前,@AutoConfigurationPackage注解没有任何属性,标注了该注解即确定了主启动类所在包(约定)。在SpringBoot 2.3.0版本以后,注解中多了两个属性,basePackages和basePackageClasses,它们可以手动指定应用的根包/根路径(配置)。如果没有手动指定,则仍然采用默认的主启动类所在包(约定大于配置)。

(1)Registrar
/**
* {@link ImportBeanDefinitionRegistrar} to store the base package from the importing configuration.
*/
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        register(registry, new PackageImports(metadata).getPackageNames().toArray(new     String[0]));
    }

    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new PackageImports(metadata));
    }

}

由以上源码可知,Registrar本身是一个ImportBeanDefinitionRegistrar,以编程式配置向IOC容器注册bean对象,而这个对象正是主启动类的包路径(方法注释中说到:store the base package)。

再来关注一下registerBeanDefinitions方法,该方法直接调用register方法,第二个参数是由PackageImports导出的一个包名数组。

(2)PackageImports
PackageImports(AnnotationMetadata metadata) {
    AnnotationAttributes attributes = AnnotationAttributes
        .fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
    List<String> packageNames = new ArrayList<>();
    // 先提取自定义的basePackages属性
    for (String basePackage : attributes.getStringArray("basePackages")) {
        packageNames.add(basePackage);
    }
    // 再提取自定义的basePackageClasses属性
    for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
        packageNames.add(basePackageClass.getPackage().getName());
    }
    // 如果都没有提取到,则提取默认的,即注解所标注的类所在包,也就是主启动类所在包
    if (packageNames.isEmpty()) {
        packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
    }
    this.packageNames = Collections.unmodifiableList(packageNames);
}

PackageImports的作用就是提取应用根包名,体现了约定大于配置。提取出根包路径后,调用外层的register方法。

(3)register方法
private static final String BEAN = AutoConfigurationPackages.class.getName();
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
    // 检查IOC容器中是否有AutoConfigurationPackages这个Bean
    if (registry.containsBeanDefinition(BEAN)) {
        // 有这个Bean时,拿到构造方法,把根包路径packageNames作为参数传进去
        BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
        ConstructorArgumentValues constructorArguments =     beanDefinition.getConstructorArgumentValues();
        constructorArguments.addIndexedArgumentValue(0,     addBasePackages(constructorArguments, packageNames));
    }
    else {
        // 没有这个Bean时,则创建这个Bean并注册到IOC容器中,把根包路径packageNames作为构造方法的参数传进去
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(BasePackages.class);
        beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,     packageNames);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(BEAN, beanDefinition);
    }
}

由以上源码可知,这两个分支逻辑的共性在于:添加构造方法的参数。这个参数就是PackageImports提取出来的根包路径packageNames。

(4)BasePackages

那根包路径保存到哪里了?从else分支可以看到,实际上构造了一个BasePackages对象,内部维护了一个字符串数组以存放这些根包路径。

/**
 * Holder for the base package (name may be null to indicate no scanning).
 */
static final class BasePackages {
    private final List<String> packages;
    BasePackages(String... names) {
        List<String> packages = new ArrayList<>();
        for (String name : names) {
            if (StringUtils.hasText(name)) {
                packages.add(name);
            }
        }
        this.packages = packages;
    }
    // ...
}
(5)保存SpringBoot应用的根包路径作用

最后一个问题,保存SpringBoot应用的根包路径用来做什么?
SpringBoot可以方便地整合第三方技术。以MyBatis为例,当项目引入mybatis-spring-boot-starter依赖后,有一个MybatisAutoConfiguration类,其中有一个AutoConfiguredMapperScannerRegistrar组件。

public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
    private BeanFactory beanFactory;

    public AutoConfiguredMapperScannerRegistrar() {
    }

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            // ...
            // 获取应用的根包路径
            List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
            // ...
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
            builder.addPropertyValue("processPropertyPlaceHolders", true);
            builder.addPropertyValue("annotationClass", Mapper.class);
            // 放置到组件构造器中
            builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
            // ...
            registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
        }
    }

    // ...
}

由以上的源码可以,AutoConfiguredMapperScannerRegistrar是一个ImportBeanDefinitionRegistrar,用于向IOC容器注册MapperScannerConfigurer组件,用于Mapper接口扫描。在创建MapperScannerConfigurer组件时,应用的根包路径作为其中一个参数被设置进去,由此也就体现出basePackages的作用。

通过以上的分析,也就理解了SpringBoot的主启动类要在所有类的最外层。

2.5.3.3 AutoConfigurationImportSelector
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
    ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered

由源码可知,AutoConfigurationImportSelector本身是一个DeferredImportSelector。

SpringBoot源码解读与原理分析(二)模块装配 中提到过,DeferredImportSelector除了具备ImportSelector的作用,它的执行时机也比ImportSelector更晚,所以更适合做一些补充性的工作。同时它还添加了分组的概念。

自动装配的设计是约定大于配置,项目中已经有的配置不会再重复注册,项目中没有配置的部分会予以补充,而负责补充的任务是交给自动配置类的,AutoConfigurationImportSelector就是加载这些自动配置类。

(1)AutoConfigurationGroup

DeferredImportSelector中真正起作用的方法是getImportGroup方法:

@Override
public Class<? extends Group> getImportGroup() {
    return AutoConfigurationGroup.class;
}

AutoConfigurationGroup实现了DeferredImportSelector.Group接口,这个接口定义了一个process方法,这个方法才是真正负责加载所有自动配置类的入口。

private final Map<String, AnnotationMetadata> entries = new LinkedHashMap<>();
private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>();

@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 = ((AutoConfigurationImportSelector) deferredImportSelector)
        .getAutoConfigurationEntry(annotationMetadata);
    // 将加载到的自动配置类存入缓存中(一个Map集合、一个List集合)
    this.autoConfigurationEntries.add(autoConfigurationEntry);
    for (String importClassName : autoConfigurationEntry.getConfigurations()) {
        this.entries.putIfAbsent(importClassName, annotationMetadata);
    }
}

加载自动配置类的核心方法是getAutoConfigurationEntry,加载完成后,将加载到的自动配置类存入缓存中(一个Map集合、一个List集合)。

(2)getAutoConfigurationEntry
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    // 加载自动配置类
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // 自动配置类去重
    configurations = removeDuplicates(configurations);
    // 获取并移除显式配置了要排除的自动配置类
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = getConfigurationClassFilter().filter(configurations);
    // 广播AutoConfigurationImportEvent事件
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

主干逻辑:加载自动配置类→自动配置类去重→获取并移除显式配置了要排除的自动配置类→封装Entry返回。

  • 加载自动配置类 getCandidateConfigurations

这里加载自动配置类的方法就是利用SpringFramework的SPI机制,从spring.factories中提取出所有配置了@EnableAutoConfiguration的自动配置类:

/**
 * Return the auto-configuration class names that should be considered. By default
 * this method will load candidates using {@link SpringFactoriesLoader} with
 */
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 提取配置了@EnableAutoConfiguration的自动配置类
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
            getBeanClassLoader());
    // 断言是否正确获得自动配置类,提示在 META-INF/spring.factories
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
            + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}
  • 获取显式配置了要排除的自动配置类 getExclusions

获取显式配置了要排除的自动配置类有三种方式:@SpringBootApplication或@EnableAutoConfiguration注解的exclude、excludeName属性,以及全局配置文件的spring.autoconfigure.exclude属性。

/**
 * Return any exclusions that limit the candidate configurations.
 */
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    Set<String> excluded = new LinkedHashSet<>();
    // exclude属性
    excluded.addAll(asList(attributes, "exclude"));
    // excludeName属性
    excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
    // spring.autoconfigure.exclude属性
    excluded.addAll(getExcludeAutoConfigurationsProperty());
    return excluded;
}

至此,需要被加载的自动配置类全部收集完毕,并返回这些自动配置类的全限定名,存入AutoConfigurationGroup的缓存中,后续IOC容器会取出这些自动配置类并解析,完成自动配置类的装载。

2.5.4 总结

总结一下SpringBoot的核心@SpringBootApplication和自动装机制。

  • @SpringBootApplication包含@ComponentScan注解,可以默认扫描当前包及其子包下的所有组件。
  • @EnableAutoConfiguration中包含@AutoConfigurationPackage注解,可以记录最外层根包的位置,以便第三方框架整合使用。
  • @EnableAutoConfiguration导入的AutoConfigurationImportSelector可以利用SpringFramework的SPI机制加载所有自动配置类。

本节完,更多内容请查阅分类专栏:SpringBoot源码解读与原理分析

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

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

相关文章

Shiro-05-5 分钟入门 shiro 安全框架实战笔记

序言 大家好&#xff0c;我是老马。 前面我们学习了 web 安全之 Spring Security 入门教程 这次我们来一起学习下另一款 java 安全框架 shiro。 什么是Apache Shiro&#xff1f; Apache Shiro是一个功能强大且易于使用的Java安全框架&#xff0c;它为开发人员提供了一种直…

【深度学习笔记】深度学习训练技巧——处理过拟合

处理过拟合 过拟合 定义&#xff1a;对训练集拟合得很好&#xff0c;但在验证集表现较差 神经网络 通常含有大量参数 (数百万甚至数十亿), 容易过拟合 处理策略&#xff1a;参数正则化、早停、随机失活、数据增强 早停 当发现训练损失逐渐下降&#xff0c;但验证集损失逐渐…

【项目管理】CMMI-项目监督和控制

项目监督和控制&#xff08;Monitoring and Control, MC&#xff09;的目的是通过周期性地跟踪项目计划的各种性能参数如工作产品的规模、工作量、成本、进度、风险等&#xff0c;不断地了解项目的进展情况&#xff0c;以便当项目实际进展状况显著偏离项目计划时能够及时采取纠…

Apache Flink连载(二十八):Flink细粒度资源管理(1)-适用场景和原理

🏡 个人主页:IT贫道-CSDN博客 🚩 私聊博主:私聊博主加WX好友,获取更多资料哦~ 🔔 博主个人B栈地址:豹哥教你学编程的个人空间-豹哥教你学编程个人主页-哔哩哔哩视频 目录

Hack The Box-Office

端口扫描&信息收集 使用nmap对靶机进行扫描 nmap -sC -sV 10.10.11.3开放了80端口&#xff0c;并且注意到该ip对应的域名为office.htb&#xff0c;将其加入到hosts文件中访问之 注意到扫描出来的还有robots文件&#xff0c;经过尝试后只有administrator界面是可以访问的 …

程序员的自我修养笔记

导读 本书将详细描述现在流行的Windows和Linux操作系统下各自的可执行文件、 目标文件格式&#xff1b; 普通C/C程序代码如何被编译成目标文件及程序在目标文件中如何存储&#xff1b; 目标文件如何被链接器链接到一起&#xff0c; 并且形成可执行文件&#xff1b; 目标文件在…

大白话说说redux

redux的3个重要概念 store 就是用来存放应用的各种状态的action 就是用来描述应用发生了什么动作的&#xff0c;注意理解他是对动作的描述reducer 就是用来处理应用的动作&#xff0c;并且决定怎么去更新应用存放在store里面的状态。 redux的3个原则 应用的所有状态存储为re…

消息中间件之RocketMQ源码分析(十)

Namesrv启动流程 第一步:脚本和启动参数配置。 启动命令 nohup ./bin/mqnamesrv -c ./conf/namesrv.conf > dev/null 2>&1 & 通过脚本配置启动基本参数&#xff0c;比如配置文件路径、JVM参数&#xff0c;调用NamesrvStartup.main()方法&#xff0c;解析命令行的…

Vue24 收集表单数据 实例

实例 <!DOCTYPE html> <html><head><meta charset"UTF-8" /><title>收集表单数据</title><script type"text/javascript" src"../js/vue.js"></script></head><body><!-- 收集…

Spring Security基础学习

一、SpringSecurity框架简介 二、SpringSecurity入门案例 三、SpringSecurity Web权限方案 四、SpringSecurity微服务权限方案 五、SpringSecurity原理总结

Eclipse - Expressions Add Watch Expression

Eclipse - Expressions & Add Watch Expression References Window -> Show View -> Other… Show View -> Debug -> Expressions -> Open Debug 模式下出现 Expressions 窗口 Debug 模式下&#xff0c;如果需要查看指定变量或者返回函数的值&#xff0c;直…

千分位分隔?一个vue指令搞定

说在前面 &#x1f388;对数字进行千分位分隔后展示应该是大部分同学都做过的功能了吧&#xff0c;常规的做法通常是编写一个工具函数来对数据进行转换&#xff0c;那么我们可不可以通过vue指令来实现这一功能呢&#xff1f; 效果展示 实现原理 非输入框 非输入框我们只需要对…

力扣题目训练(16)

2024年2月9日力扣题目训练 2024年2月9日力扣题目训练530. 二叉搜索树的最小绝对差541. 反转字符串 II543. 二叉树的直径238. 除自身以外数组的乘积240. 搜索二维矩阵 II124. 二叉树中的最大路径和 2024年2月9日力扣题目训练 2024年2月9日第十六天编程训练&#xff0c;今天主要…

【白嫖8k买的机构vip教程】python(2):python_re模块

python之re模块 一、正则表达式   re模块是python独有的匹配字符串的模块&#xff0c;该模块中提供的很多功能是基于正则表达式实现的&#xff0c;而正则表达式是对字符串进行模糊匹配&#xff0c;提取自己需要的字符串部分&#xff0c;他对所有的语言都通用。注意&#xf…

程序员必看的几部电影

目录 《我是谁&#xff1a;没有绝对安全的系统》 《模仿游戏》 《硅谷传奇》 《代码 The Code》 作为程序员&#xff0c;除了在工作中不断学习和提升技术外&#xff0c;适当地放松也是必不可少的 看电影可以是一个很好的放松方式&#xff0c;而对于程序员来说&#xff0c;…

GO框架基础 (一)、MySQL数据库

什么是数据库 数据库是一个组织化的数据集合&#xff0c;它被设计为方便存储、管理和检索数据。数据库通常以表格的形式组织数据&#xff0c;其中每一行代表一个数据记录&#xff0c;每一列代表一个数据字段。数据库系统提供了一种结构化的方法来存储和管理数据&#xff0c;以…

Android 基础技术——Binder 机制

笔者希望做一个系列&#xff0c;整理 Android 基础技术&#xff0c;本章是关于Binder 机制 什么是Binder 机制&#xff1a;Binder 是一种进程间通信机制 驱动&#xff1a;Binder 是一个虚拟物理设备驱动 应用层&#xff1a;Binder 是一个能发起通信的 Java 类 为什么要使用Bind…

Springboot返回给前端的日期变成时间戳问题处理

问题&#xff1a; 解决方案 在对应的Entity类中&#xff0c;给对应属性添加JsonFormat​注解 ​JsonFormat​注解用于将Date日期格式化为指定格式的字符串。由于在序列化时间时是按照国际标准时间GMT进行格式化的&#xff0c;最后接受到的数据会早8个小时&#xff0c;所以应该…

力扣 188. 买卖股票的最佳时机 IV

题目来源&#xff1a;https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/description/ C题解&#xff1a;动态规划 思路同力扣 123. 买卖股票的最佳时机 III-CSDN博客&#xff0c;只是把最高2次换成k次。如果思路不清晰&#xff0c;可以将k从0写到4等找找规律…

FPGA转行ISP的探索之一:行业概览

ISP的行业位置 最近看到一个分析&#xff0c;说FPGA的从业者将来转向ISP&#xff08;Image Signal Process图像信号处理&#xff09;是个不错的选择&#xff0c;可以适应智能汽车、AI等领域。故而我查了一下ISP&#xff0c;对它大致有个概念。 传统的ISP对应的是相机公司&…