springboot 注解_SpringBoot启动注解简析

SpringBoot中遍布注解,如果无法理解其作用,也就相当于主动放弃了其强大的灵活性,它核心的“组合”一说也就无从谈起

源头——@SpringBootApplication

springboot 注解_SpringBoot启动注解简析_第1张图片
  • @SpringBootApplication是SpringBoot开启的源头注解,进入源代码,发现除去元注解,剩余三个Spring包下的注解,分别为:
    • @SpringBootConfiguration
    • @EnableAutoConfiguration
    • @ComponentScan

第一个注解@SpringBootConfiguration

进入@SpringBootConfiguration,一共以下三个注解:

  • @Target(ElementType.TYPE)
  • @Retention(RetentionPolicy.RUNTIME)
  • @Documented
  • @Configuration

最上面的三个元注解和@SpringBootConfiguration是相同的,仅剩下一个注解@Configuration,这个注解非常熟悉,它就是Spirng中的“注解类”注解,因此,@SpringBootConfiguration本质上就是一个@Configuration,没必要再深入分析了。


第二个注解@EnableAutoConfiguration

springboot 注解_SpringBoot启动注解简析_第2张图片

Spring中以@Enable开头的注解有一个共同特点——通过@Import注解引入特定的JavaConfig类,再对其进行注册

该注解除去元注解后,剩余:

- @AutoConfigurationPackage
- @Import(AutoConfigurationImportSelector.class)

第一个@AutoConfigurationPackage从名字就可以看出来——自动配置包——它必然就是源头注解可以自动扫描的核心注解。

点开源代码如下:

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {}

发现除去元注解,仅存在一个@Import注解

根据注释说明,知道了这个类用作储存自动配置的包,用作之后参考,其源码为一个抽象类,它所调用的静态内部类Registrar的注释为:

to store the base package from the importing configuration.

到这里就很明显了,根据Registrar的注释说明,它会重写实现的接口ImportBeanDefinitionRegistrar,从而存储导入的基础包,在重写的方法registerBeanDefinitions()处打上断点,可以看到:

springboot 注解_SpringBoot启动注解简析_第3张图片

可见此方法将自己的项目添加进了SpringBoot Application。


于是我们再回到@EnableAutoConfiguration内的第二个注解@Import中,显然,这又是一个导入注解,因此,我们只关心它导入的是什么就ok。

进入@Import导入的@AutoConfigurationImportSelector类,这个类的代码有几百行,但需要关注的就仅有几个方法:

首先从下面的方法开始探索:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
      
    if (!isEnabled(annotationMetadata)) {
      
        return NO_IMPORTS;
    }
    try {
      
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        List configurations = getCandidateConfigurations(annotationMetadata,
                attributes);
        configurations = removeDuplicates(configurations);
        configurations = sort(configurations, autoConfigurationMetadata);
        Set exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return configurations.toArray(new String[configurations.size()]);
    }
    catch (IOException ex) {
      
        throw new IllegalStateException(ex);
    }
}

注意上面的List configurations 列表,它首先会预加载SpringBoot所有组件名:

springboot 注解_SpringBoot启动注解简析_第4张图片

具体通过下面的方法实现:

protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
      
    List configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
            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;
}

将配置存储在configurations中,而该变量通过SpringFactoriesLoader类处理,于是需要进去看看源码,我们关心的是loadFactoryNames这个方法:

public static List loadFactoryNames(Class factoryType, @Nullable ClassLoader classLoader) {
      
    String factoryTypeName = factoryType.getName();
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

从返回值中,注意到loadSpringFactories这个方法,它也在现在我们进入的这个类中:

private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
      
    MultiValueMap result = (MultiValueMap)cache.get(classLoader);
    if (result != null) {
      
        return result;
    } else {
      
        try {
      
            // 注意下面这行代码,它会从 META-INF/spring.factories 这个文件中获取需要的加载的资源
            Enumeration urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            LinkedMultiValueMap result = new LinkedMultiValueMap();
            // 下面的while会找到需要加载资源备用
            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);
        }
    }
}

springboot 注解_SpringBoot启动注解简析_第5张图片

然后,通过filter(configurations, autoConfigurationMetadata);方法实现了真正需要加载的组件:

springboot 注解_SpringBoot启动注解简析_第6张图片

至于这个筛选是怎么进行的,下面继续探究。


不难发现,这些组件有个共同特点,它们的组件名都是“xxxxAutoConfiguration”,说明它们必然有些被加载、筛选的共性。

找到项目路径:

springboot 注解_SpringBoot启动注解简析_第7张图片

任意点开spring.factories资源文件的一个类:

ffa942d1b114cf02e5f708b69de4b6d3.png

@ConditionalOnProperty这个注解又什么作用呢,进入之后,发现除去元注解,只有一个@Conditional,顾名思义,就是这个配置有使用条件。点进这个注解有这么一行注释说明:

Indicates that a component is only eligible for registration when all specified conditions match.

猜想是对的,spring.factories资源文件下的很多资源都是有使用条件的,因此并不会全部加载。


又要回到源头注解@SpringBootApplication中了,只剩最后一个注解@ComponentScan了,简单说明一下就好。

  • 这个注解是Springframework下的一个注解,在Spring中用于扫描需要用到的带有
    • @Controller
    • @Service
    • @Repository
    • @Component

类似注解的包,在@SpringBootApplication中,这个注解内的参数为:

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

excludeFilters 这个参数为设置不需要扫描的包。

你可能感兴趣的:(springboot,注解)