一篇文章搞清楚@SpringBootApplication

504 阅读5分钟

SpringBootApplication注解解析

@SpringBootApplication 其实是由三个注解组成的

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

为了理解@SpringBootApplication这个注解,我们将目标拆分,挨个分析组成@SpringBootApplication的注解

首先来看@SpringBootConfiguration

@SpringBootConfiguration

@Configuration//这里表示SpringBootConfiguration这个类是一个配置类
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

因为@Configuration修饰@SpringBootConfiguration,而@SpringBootConfiguration 修饰 @SpringBootApplication

而@SpringBootApplication 修饰 XxxMainApplication,所以XxxMainApplication其实也相当于被@Configuration修饰了,那么XxxMainApplication其实也就是个配置类==(套娃注解)==

image-20210421152100501

@ComponentScan

注解扫描

@EnableAutoConfiguration

前面两个都意义都不是很大,这个才是硬菜

我们从这个注解的声明来看,可以发现这个注解其实也是一个复合注解,它被

  1. @AutoConfigurationPackage
  2. Import修饰着,我们前面讲过@Import可以自动的调用指定类的无参构造器创建对象,并将其放在IOC容器中,这里就是将AutoConfigurationImportSelector这个类的对象放入IOC容器中了
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration

第一条支线:@AutoConfigurationPackage

image-20210421195310197

  1. 进入@AutoConfigurationPackage的源码中我们可以看到,这个类通过@Import导入了Registrar.class
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}
  1. 进入Registrar这个类,我们发现这是一个静态内部类
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {}

​ 这个类中有一个重要的方法registerBeanDefinitions

​ 这个方法传入了两个参数:(1)AnnotationMetadata metadata;(2)BeanDefinitionRegistry registry

​ 这个方法的目的是什么呢?其实是:==往容器中批量注册组件==

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

为了弄清楚这个方法中各变量的值,我们在方法体内部打一个断点,如图所示

image-20210421155844120

我们通过idea计算表达式结果小技巧来看看这个表达式获得的值是什么

new AutoConfigurationPackages.PackageImports(metadata).getPackageNames()

image-20210421161453454

计算之,这里获得的是一个包名:com.zztracy.academy.adademyblog,正好是主启动类XxxMainApplication.class所在的包

image-20210421161757881

那么此时我们知道了扫描的包的位置

另外,根据刚才的debug,我们还可以从控制台看到三个变量

  1. this
  2. metadata
  3. registry

image-20210421155915746

其中metadata中存储的是@AutoConfigurationPackage注解的元信息,包括(1)注解标在了哪;(2)它的每一个值是什么,上面我们讲到==(套娃注解)==,那么就相当于这个注解的元信息其实指的也是@SpringBootApplication的元信息

introspected 内省;内观

image-20210421160531991

image-20210421161103052

点开registry,找到beanDefinitionNames,我们可以看到这是一个ArrayList,里面存储的是IOC组件的名字,

其中我们看到的组件名为全类名的,一般为SpringBoot自动导入的??(有待商榷)

而没有全类名的,则是自己编写的,从侧面来说@Component @Controller @Service等都会将对应的类注册进IOC容器中,并且实例的名字为类名

image-20210421160052991

计算出参数之后,再通过这个register方法,批量将组件注入容器中

AutoConfigurationPackages.register(需要扫描的包的位置)

综上所述,这个方法的意义在于

获取注解元信息的包名 -> 转为String数组 -> 通过register这个方法,扫描这个包下面的所有符合条件的组件,注册进IOC容器中

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

第二条支线:@Import({Autoxxx.class})

总的作用:读取springboot启动时需要自动配置的所有类的全类名,但是是否加入到IOC容器中还需要看@Condition相关的注解

@Import({AutoConfigurationImportSelector})

先来看看总的方法调用图,我们可以看到@EnableAutoConfiguration生效之前,一共需要经历6个步骤。其中在最前面的就是@Import({AutoConfigurationImportSelector})中文翻译 自动配置导入选择器,在最后面的是loadSpringFactories()方法,这个也是最终最重要的方法,我们后面会讲到。

image-20210421203428766

自顶向下的看

  1. 首先ctrl+左键,进入AutoConfigurationImportSelector这个类,
  2. 直接找到selectImports()这个核心方法

代码如下:

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {//走else逻辑
        AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}
  1. 进入getAutoConfigurationEntry()方法
//核心方法 getAutoConfigurationEntry 获取需要自动配置的组件信息
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {//走else逻辑
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            //获取组件,并且封装为String集合,重点代码
            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 AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

在这个方法体的第一行打个断点,debug一下

可以看到类型为List的变量configurations看到一共有261个组件(这个依据不同项目的pom文件情况有所不同)

image-20210421164121972

  1. 接着进入 getCandidateConfigurations
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;
}
  1. 再进入loadFactoryNames方法
 public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }
  1. 再进入loadSpringFactories方法

最终就是通过这个方法得到一个Map,这个方法的逻辑是从所有maven引入的包下的META-INF/spring.factories这个文件中读取信息

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {//走else逻辑,总的就是遍历所有的maven导入的jar包中META-INF路径下的spring.factories文件中的所有内容
        try {
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            LinkedMultiValueMap result = new LinkedMultiValueMap();

            while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                Iterator var6 = properties.entrySet().iterator();

                while(var6.hasNext()) {
                    Entry<?, ?> entry = (Entry)var6.next();
                    String factoryTypeName = ((String)entry.getKey()).trim();
                    String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                    int var10 = var9.length;

                    for(int var11 = 0; var11 < var10; ++var11) {
                        String factoryImplementationName = var9[var11];
                        result.add(factoryTypeName, factoryImplementationName.trim());
                    }
                }
            }

            cache.put(classLoader, result);
            return result;
        } catch (IOException var13) {
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
        }
    }
}

我们看一下maven导入的jar包,有些jar包没有spring.factories这个文件,例如:

image-20210421170148577

有些jar包有spring.factories这个文件,其中最核心的是 springboot的autoconfigure

image-20210421170527413

打开spring.factories我们可以看到许多配置信息,其中我们看到EnableAutoConfiguration

我们可以看到有很多项,并且每一项后面都有换行符,我根据行号数了一下:148 - 22 + 1 = 127

也就是说springboot在这个地方写死一启动就要给容器加载的127个场景下的自动配置类

然而实际上,这些自动配置类所在位置,有很多的@Condition以及其派生注解的修饰,也就是说这些加载的自动配置,最终调用器构造器,注入到IOC容器中的,并不是127个

image-20210421171135433

篇幅太长,只截取了前10个

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\

总结:

  1. 利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件

  2. 利用List configruations = getCandidateConfigurations(annotationMetadata, attributes) 获取到所有需要导入到IOC容器的组件

  3. 利用工厂加载 ==private static Map<String, List> loadSpringFactories(@Nullable ClassLoader classLoader)==得到所有的组件

  4. 从META-INF/spring.factories位置加载一个文件

    默认扫描当前系统中所有META-INF/spring.factories位置下的文件