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

源头——@SpringBootApplication




springboot注解开启定时器 springboot注解启动_springboot 注解


  • @SpringBootApplication是SpringBoot开启的源头注解,进入源代码,发现除去元注解,剩余三个Spring包下的注解,分别为:
  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

第一个注解@SpringBootConfiguration

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

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

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


第二个注解@EnableAutoConfiguration


springboot注解开启定时器 springboot注解启动_springboot注解开启定时器_02


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注解启动_springboot注解开启定时器_03


可见此方法将自己的项目添加进了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<String> configurations = getCandidateConfigurations(annotationMetadata,
                attributes);
        configurations = removeDuplicates(configurations);
        configurations = sort(configurations, autoConfigurationMetadata);
        Set<String> 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<String> configurations 列表,它首先会预加载SpringBoot所有组件名:


springboot注解开启定时器 springboot注解启动_springboot 注解_04


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


protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> 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<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    String factoryTypeName = factoryType.getName();
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}


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


private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
    if (result != null) {
        return result;
    } else {
        try {
            // 注意下面这行代码,它会从 META-INF/spring.factories 这个文件中获取需要的加载的资源
            Enumeration<URL> 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注解启动_springboot注解开启定时器_05


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


springboot注解开启定时器 springboot注解启动_加载_06


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


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

找到项目路径:


springboot注解开启定时器 springboot注解启动_springboot 注解_07


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


springboot注解开启定时器 springboot注解启动_加载_08


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

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

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


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

  • @Controller
  • @Service
  • @Repository
  • @Component

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


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


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