[SpringBoot源码分析一]:@SpringBootApplication

众所周知我们要想启动一个SpringBoot项目,至少需要像下面的方式启动 而这里就在启动类上标记了@SpringBootApplication

1. @SpringBootApplication全貌

上来我们先看此注解的样子

java 复制代码
...
@SpringBootConfiguration
@EnableAutoConfiguration(exclude = AssertionError.class)
@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 = Configuration.class)
   boolean proxyBeanMethods() default true;

   Component aa() default @Component;
}

我们可以发现这个注解上带有了三个重要的注解

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

这三个注解其实才是@SpringBootApplication的本体,接下来我们一一介绍

2. @SpringBootConfiguration

这个注解的很简单,本质上就是一个翻版的@Configuration

java 复制代码
...
@Configuration
public @interface SpringBootConfiguration {

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

}

但是这个注解又必须出现在@SpringBootApplication中,因为在ConfigurationClassPostProcessor中会执行下面这样代码

而这个方法中就会要求必须携带@Configuration

3. @ComponentScan

@ComponentScan :在默认的情况下扫描和启动类包路径一样的class文件,并检查是否带有@Component@ManagedBean, 一旦带有那么就注册到容器中

java 复制代码
public @interface ComponentScan {

   /**
    * 包扫描的路径
    */
   @AliasFor("basePackages")
   String[] value() default {};

   /**
    * 包扫描的路径
    */
   @AliasFor("value")
   String[] basePackages() default {};

   /**
    * 从指定的Class的包路径开始扫描
    */
   Class<?>[] basePackageClasses() default {};

   /**
    * bean名称生产器
    */
   Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

   /**
    * 范围解析器:检查bean是否有@Scope注解注解的解析器
    */
   Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

   /**
    * 如果bean是多例,以什么方式创建
    */
   ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

   /**
    * 是一个关于文件类型的过滤,默认是 * * / *.class
    */
   String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;

   /**
    * 是否使用默认的filter扫描类,默认可以扫描@Component@Repository、@Service或@Controller注解的类。
    */
   boolean useDefaultFilters() default true;

   /**
    * 指定符合扫描条件:为了进一步缩小候选bean的范围
    */
   Filter[] includeFilters() default {};

   /**
    * 指定不符合组件扫描条件
    */
   Filter[] excludeFilters() default {};

   /**
    * 注册的bean是否延迟初始化。
    */
   boolean lazyInit() default false;
   ...
}

此时让我们将目光回到@SpringBootApplication中的@ComponentScan,我们会发现他携带了两个Filter

  • TypeExcludeFilter
  • AutoConfigurationExcludeFilter

要想了解这两个类得先理解他们实现的接口 TypeFilter

java 复制代码
@FunctionalInterface
public interface TypeFilter {

   boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
         throws IOException;

}

分析这个类的类名和方法就能得出:当我们不需要某些Bean注册到容器中的时候,可以指定此过滤器,然后就可以根据类型进行过滤

2.1 TypeExcludeFilter

接下来我们直接看这个类的源码

java 复制代码
public class TypeExcludeFilter implements TypeFilter, BeanFactoryAware {
   ...
   @Override
   public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
         throws IOException {
      if (this.beanFactory instanceof ListableBeanFactory && getClass() == TypeExcludeFilter.class) {
         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) {
         delegates = ((ListableBeanFactory) this.beanFactory).getBeansOfType(TypeExcludeFilter.class).values();
         this.delegates = delegates;
      }
      return delegates;
   }
}

我们可以发现这个类是一个带有委托机制的类,他的getDelegates()方法用于获取到容器中类型为TypeExcludeFilter的类,再去处理

2.2 AutoConfigurationExcludeFilter

上来我们先看此类的源码

java 复制代码
public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware {

   private ClassLoader beanClassLoader;

   //自动配置类集合
   private volatile List<String> autoConfigurations;

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

   //如果是配置类而且是自动配置类就返回true
   @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) {
      return getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());
   }

   /**
    * 实际上是获取的所有的自动配置类
    * @return
    */
   protected List<String> getAutoConfigurations() {
      if (this.autoConfigurations == null) {
         this.autoConfigurations = SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class,
               this.beanClassLoader);
      }
      return this.autoConfigurations;
   }

}

分析源码我们可以得知这是一个排除自动配置类的TypeFilter

  • 实际上自动配置类都在org.springframework.boot.autoconfigure路径下,默认情况下@ComponentScan是以启动类作为默认路径进行扫描,所以说这个TypeFilter 条件一直为false
  • 并且自动配置类实际上是由于 @EnableAutoConfiguration 导入的 AutoConfigurationImportSelector负责注册的

4. @EnableAutoConfiguration

@EnableAutoConfiguration是SpringBoot中开启自动配置类的入口注解

java 复制代码
...
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

   String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

   /**
    * 排除某些自动配置类,传入的是Class对象
    */
   Class<?>[] exclude() default {};

   /**
    * 排除某些自动配置类,传入的是类名
    */
   String[] excludeName() default {};

}

分析源码我可以得知这个注解重点就是引入了下面这两个东西

  • @AutoConfigurationPackage
  • AutoConfigurationImportSelector

5. @AutoConfigurationPackage

我们看@AutoConfigurationPackage发现他导入了一个Registrar

java 复制代码
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

然后我们再看Registrar,发现他实现了两个接口ImportBeanDefinitionRegistrarDeterminableImports

java 复制代码
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

   @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));
   }

}

5.1 @ImportBeanDefinitionRegistrar

我们先来介绍下ImportBeanDefinitionRegistrar接口,这个类的作用我们看名字就知道,这是一个注册BeanDefinition的类,这个类会在ConfigurationClass转换为BeanDefinition的阶段被调用

java 复制代码
public interface ImportBeanDefinitionRegistrar {

    /**
    * @param importingClassMetadata 导入类的注解元数据
    * @param registry 当前Bean的注册中心,默认为 {@link org.springframework.beans.factory.support.DefaultListableBeanFactory DefaultListableBeanFactory}
    * @param importBeanNameGenerator Bean名称生成器策略
    */
   default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
         BeanNameGenerator importBeanNameGenerator) {

      registerBeanDefinitions(importingClassMetadata, registry);
   }
   ...
}

知道了Registrar的执行逻辑,我们再看其register方法干了什么

分析其代码我们可以知道这是一个将启动类的包路径包装为BasePackages类,并注册到容器中 ,以供后续使用

java 复制代码
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
   if (registry.containsBeanDefinition(BEAN)) {
      BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
      ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
      //给这个构造方法的第一个参数设置为包路径
      constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
   }
   else {
      GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
      beanDefinition.setBeanClass(BasePackages.class);
      //给这个构造方法的第一个参数设置为包路径
      beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
      //设置角色为 框架
      beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
      //执行注册
      registry.registerBeanDefinition(BEAN, beanDefinition);
   }
}

6. AutoConfigurationMetadataLoader

@AutoConfigurationImportSelector是实际上负责注册自动配置类的类

在讲这个Selector之前我们需要先了解两个类,其中第一个是负责从META-INF/spring.factories中加载自动配置类名称的SpringFactoriesLoader

  • SpringFactoriesLoader
  • AutoConfigurationMetadataLoader

至于另外一个,大家想想一个场景,当spring.factories中有SpringSecurity的自动配置类,但是我们的项目中并没有导入SpringSecurity的jar包,那这个自动配置类应该工作吗,或者说A自动配置类可能要求在B自动配置类以前初始化又怎么办?

在这种场景下AutoConfigurationMetadataLoader应运而生,是负责从META-INF/spring-autoconfigure-metadata.properties中加载自动配置类规则

那我们再看看这个properties的庐山真面目,刚好在spring-boot-actuator-autoconfigure中有这个文件

这个文件比较长,我总结了它的规则如下:A和B都是自动配置类的包路径+名称

  • A.ConditionalOnClass=类名:A起作用需能够加载某个类
  • A.ConditionalOnBean=Bean名称:A起作用需要容器中某个Bean
  • A.ConditionalOnWebApplication=SERVLET:A起作用要求当前环境为Servlet
  • A.AutoConfigureAfter=B:A的初始化需要在B之后
  • A.AutoConfigureBefore=B:A的初始化需要在B之前

至此我们知道了spring.factoriesspring-autoconfigure-metadata.properties的作用,但是这两个东西最终还是靠AutoConfigurationImportSelector工作的

7. AutoConfigurationImportSelector

@AutoConfigurationImportSelector是实际上负责注册自动配置类的类,我们先看下这个类的结构

虽然这个类实现了很多接口,但是大部分都是什么Aware,这种接口都是直接设置对应的对象,不理解也不会影响到这个类的逻辑,但是其中有一个DeferredImportSelector, 这个接口翻译过来就是延时导入的意思,一般情况下我们Bean的初始化都是在我们下面的第一个红框中处理的,但是DeferredImportSelector的Bean确是在我标记的第二个红框中处理的

为什么要这样设计呢,我理解是因为@ConditionalOnBean这种注解的情况下,自动配置类的条件计算至少要等到其他Bean处理完毕,才可以处理自动配置类

在这种情况下ImportSelector的selectImports(...)方法反而不会执行,而getAutoConfigurationEntry(...)会被执行,这个方法会返回符合规则的自动配置类

java 复制代码
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
      AnnotationMetadata annotationMetadata) {
   //判断是否开启了自动配置选项
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   //获得有关@EnableAutoConfiguration的属性
   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 = filter(configurations, autoConfigurationMetadata);
   //推送自动配置类导入事件
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

重要的代码我都已经写了注释,但是其中有一个filter(...)方法需要单独讲一下

java 复制代码
/**
 * 获取bean工厂的AutoConfigurationImportFilter,进行过滤
 * spring.factories表示要导入的自动配置类,而spring-autoconfigure-metadata.properties中是自动配置类的规则
 * @param configurations 从META-INF/spring.factories中读取到的有关EnableAutoConfiguration的类
 * @param autoConfigurationMetadata 从META-INF/spring-autoconfigure-metadata.properties中读取到的类
 * @return
 */
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
   long startTime = System.nanoTime();
   //候选自动配置类名称
   String[] candidates = StringUtils.toStringArray(configurations);
   //不需要自动配置名称的集合,顺序和configurations是对应起来的
   boolean[] skip = new boolean[candidates.length];
   //是否需要跳过某些自动配置类的标志位,默认不跳过
   boolean skipped = false;

   //执行所有的过滤器
   for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
      //执行有关Aware的方法
      invokeAwareMethods(filter);
      //重点:进行匹配
      boolean[] match = filter.match(candidates, autoConfigurationMetadata);
      for (int i = 0; i < match.length; i++) {
         //如果无法匹配
         if (!match[i]) {
            skip[i] = true;
            candidates[i] = null;
            skipped = true;
         }
      }
   }
   //完全没有需要跳过的自动配置类
   if (!skipped) {
      return configurations;
   }
   //将不需要跳过的自动配置类加入到result中
   //最终需要返回的也是result
   List<String> result = new ArrayList<>(candidates.length);
   for (int i = 0; i < candidates.length; i++) {
      if (!skip[i]) {
         result.add(candidates[i]);
      }
   }
   ...
   return new ArrayList<>(result);
}

filter(...)方法说白了就是通过AutoConfigurationImportFilter对于自动配置类进行过滤,返回符合规则的自动配置类,而AutoConfigurationImportFilter有三个重要的实现:

  • OnClassCondition
  • OnBeanCondition
  • OnWebApplicationCondition

而这里就刚好就对应了我前面讲的spring-autoconfigure-metadata.properties中的其中一部分规则

8. 总结

@SpringBootApplication的作用就是导入三个注解

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

@EnableAutoConfiguration是负责开启并注册自动配置类,@ComponentScan是最终在ConfigurationClassPostProcessor中负责注册我们自定义的Bean的

相关推荐
阿伟*rui2 小时前
配置管理,雪崩问题分析,sentinel的使用
java·spring boot·sentinel
paopaokaka_luck4 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
Yaml46 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~6 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616886 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
程序媛小果7 小时前
基于java+SpringBoot+Vue的旅游管理系统设计与实现
java·vue.js·spring boot
AskHarries9 小时前
Spring Boot集成Access DB实现数据导入和解析
java·spring boot·后端
2401_8576226610 小时前
SpringBoot健身房管理:敏捷与自动化
spring boot·后端·自动化
程序员阿龙10 小时前
基于SpringBoot的医疗陪护系统设计与实现(源码+定制+开发)
java·spring boot·后端·医疗陪护管理平台·患者护理服务平台·医疗信息管理系统·患者陪护服务平台
前 方10 小时前
若依入门案例
java·spring boot·maven