源码拆解SpringBoot的自动配置机制

发布于:2024-07-26 ⋅ 阅读:(73) ⋅ 点赞:(0)

SpringBoot相比于Spring系列的前作,很大的一个亮点就是将配置进行了简化,引入了自动化配置,仅靠几个注解和yml文件就取代了之前XML的繁琐配置机制,这也是SpringBoot的独有特点,下面我们从源码角度,一点点拆开自动配置的机制是如何实现的。

从@SpringBootApplication开始

从SpringBoot项目初始类上的SpringBootApplication注解开始

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "nameGenerator"
    )
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

发现SpringBootApplication注解是一个入口,里面涵盖了很多关于SpringBoot的启动注解,和自动化配置相关的主要是@EnableAutoConfiguration@ComponentScan

  • @EnableAutoConfiguration:这个注解是Spring Boot自动配置的入口点。它指示Spring Boot启动自动配置过程。
  • @ComponentScan:允许你指定一个或多个包,Spring容器将扫描这些包以及其子包中带有@Component@Service@Repository@Controller等注解的类。

 @EnableAutoConfiguration

打开这个注解的源码,看一下此注解主要做了什么

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

作为主要管控SpringBoot自动配置化的复合注解,抛开常用的,@EnableAutoConfiguration中主要的注解是@AutoConfigurationPackage
@Import其中@AutoConfigurationPackage 用于指示Spring Boot自动配置应该从哪个包开始扫描,是一个Spring扫描类的注解。 @Import则是自动配置的核心逻辑入口,源码中通过@Import导入了一个名为 AutoConfigurationImportSelector的类,而这个类 就是自动配置的加载选择器。

自动配置类的筛选流程

下面看一下AutoConfigurationImportSelector这个选择器主要干了什么:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
  
    private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();
    private static final String[] NO_IMPORTS = new String[0];
    private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
    //……省略部分成员变量

   protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        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 AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.getConfigurationClassFilter().filter(configurations);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationEntry(configurations, exclusions);
        }
    } 

}

这里AutoConfigurationImportSelector的内容太多,我只摘取了与自动配置相关的逻辑,关于自动化配置加载,主要是getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,AnnotationMetadata annotationMetadata)方法,该方法决定哪些自动配置类应该被导入到Spring的Bean定义中。方法的入参为AutoConfigurationMetadataAnnotationMetadata对象,这两个参数提供了自动配置的元数据和注解的元数据,对接外部配置注解与加载机制的连接。后续的逻辑处理需要用到配置信息。

核心流程

首先根据外部参数annotationMetadata来确定整个系统是否开启了自动化加载机制,这里其实是判断@EnableAutoConfiguration注解或spring.boot.enableautoconfiguration属性的设置,在SpringBoot中可以通过这两种方式决定是否启用自动化配置

然后读取@EnableAutoConfiguration注解的控制信息,根据配置信息进行确定加载哪些自动化配置类(携带@Configuration注解的类或者XML文件),排除哪些不需要的配置类(@EnableAutoConfiguration注解指定控制排除一些自动配置);

后面进行容错机制,包含去除重复的类、筛选出要排除的自动化配置类。根据自动配置元数据进一步过滤配置列表;

触发自动配置导入事件,允许其他监听器在自动配置类被导入之前进行操作后创建并返回一个AutoConfigurationEntry对象,它包含了最终确定要导入的自动配置类列表和被排除的类列表。 

返回对象的处理

返回的AutoConfigurationEntry 实体对象,它包含了最终确定要导入的自动配置类列表和被排除的类列表。这个对象最终会被注册为Spring容器中的一个Bean(Spring中万物皆bean的思想)

AutoConfigurationEntry 是一个内部类,在AutoConfigurationImportSelector类中定义源码如下:

protected static class AutoConfigurationEntry {
        private final List<String> configurations;
        private final Set<String> exclusions;

        private AutoConfigurationEntry() {
            this.configurations = Collections.emptyList();
            this.exclusions = Collections.emptySet();
        }

        AutoConfigurationEntry(Collection<String> configurations, Collection<String> exclusions) {
            this.configurations = new ArrayList(configurations);
            this.exclusions = new HashSet(exclusions);
        }

        public List<String> getConfigurations() {
            return this.configurations;
        }

        public Set<String> getExclusions() {
            return this.exclusions;
        }
    }

随后Spring的Bean管理会对AutoConfigurationEntry 对象内的候选配置类集合(List<String> configurations)中的类逐一加载,加载中处理每个类上的特殊逻辑, 例如,某个自动配置类的触发条件等等(上篇文章中的LogNoteCondition 的逻辑即是在此处处理的);最后这些自动配置类也会变成bean被加载到Spring的上下文中。被排除的自动配置类(Set<String> exclusions)会被记录,但不会被导入。Spring Boot 会确保这些类在自动配置过程中被忽略。

我们随便启动一个SpringBoot项目,在Debug模式下可以看到处理结束的候选配置类和排除的配置类: 

XXXConfiguration和ConfigurationProperties的注入

debug 里,我们看到了成功装配了AutoConfigurationEntry 对象内的候选配置类,但是这里只是配置类的路径,却还没有配置类的实例对象以及对象内的自动配置值,基于上述的debug,我们选择ServletWebServerFactoryAutoConfiguration进行跟踪

@Configuration(
    proxyBeanMethods = false
)
@AutoConfigureOrder(Integer.MIN_VALUE)
@ConditionalOnClass({ServletRequest.class})
@ConditionalOnWebApplication(
    type = Type.SERVLET
)
@EnableConfigurationProperties({ServerProperties.class})
@Import({BeanPostProcessorsRegistrar.class, ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, ServletWebServerFactoryConfiguration.EmbeddedJetty.class, ServletWebServerFactoryConfiguration.EmbeddedUndertow.class})
public class ServletWebServerFactoryAutoConfiguration {
   //……省略部分代码
}

这里对于ServletWebServerFactoryAutoConfiguration在进行bean加载时需要先处理@EnableConfigurationProperties(ServerProperties.class) ,它的意思是启用指定类的@ConfigurationProperties注解功能;目的是将配置文件中对应的值和 ServerProperties 绑定起来;并把
ServerProperties 加入到 IOC 容器中。

ServerProperties源码:

@ConfigurationProperties(
    prefix = "server",
    ignoreUnknownFields = true
)
public class ServerProperties {
    private Integer port;
    private InetAddress address;
    @NestedConfigurationProperty
    private final ErrorProperties error = new ErrorProperties();
    private ForwardHeadersStrategy forwardHeadersStrategy;
    private String serverHeader;
    private DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8L);
    private Shutdown shutdown;
    @NestedConfigurationProperty
    private Ssl ssl;
    @NestedConfigurationProperty
    private final Compression compression;
    @NestedConfigurationProperty
    private final Http2 http2;
    private final Servlet servlet;

   // ……省略
}

ServerProperties中使用 @ConfigurationProperties注解绑定属性映射文件中的 server 开头的属性。

也就是大部分SpringBoot的yml文件中的:

server:
  port: 8081
  servlet:
    context-path: /test
    encoding:
      charset: UTF-8
      enabled: true
      force: true
spring:
  #省略后面配置

补充:自定义starter与SpringBoot的合并过程

在 上篇自定义starter中我们对于基本启动类,需要创建META-INFO下的spring.factories文件和主启动类中的@ComponentScan(basePackages = "org.example.lognote.*")注解,这里就连上了。

对于外部使用自定义的starter的SpringBoot项目,它在@EnableAutoConfiguration注解时便会触发自动配置类的筛选流程,通过筛选流程中的getAutoConfigurationEntry方法,根据外部starter项目中的spring.factories配置找到其启动类加载到AutoConfigurationEntry 对象中,随后SpringBean加载机制处理AutoConfigurationEntry 对象中的候选配置类时,starter中的启动类自然作为这个外部SpringBoot项目中的一个简单@Configuration被加载。通过starter项目启动类上的@ComponentScan注解将整个starter中的其他bean都加载到SpringBoot项目中的上下文里。


网站公告

今日签到

点亮在社区的每一天
去签到