SpringBoot核心原理之自动配置

从@SpringBootApplication注解入手

为了揭开SpringBoot的奥秘,我们直接从Annotation入手,看看@SpringBootApplication里面,做了什么?
打开@SpringBootApplication这个注解,可以看到它实际上是一个复合注解

 1 @Target(ElementType.TYPE) 
 3 @Retention(RetentionPolicy.RUNTIME)
 5 @Documented
 7 @Inherited
 9 @SpringBootConfiguration //实际上是 @Configuration
11 @EnableAutoConfiguration
13 @ComponentScan(excludeFilters = {
15 @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), 
17 @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
19 public @interface SpringBootApplication {

SpringBootApplication 本质上是由 3 个注解组成,分别是

@Configuration
@EnableAutoConfiguration
@ComponentScan

我们直接用这三个注解也可以启动 springboot 应用,只是每次配置三个注解比较繁琐,所以直接用一个复合注解更方便些。

然后仔细观察这三个注解,除了 @EnableAutoConfiguration 可能稍微陌生一点,其他两个注解使用得都很多。

简单分析@Configuration

@Configuration这个注解大家应该有用过,它是JavaConfig形式的基于Spring IOC容器的配置类使用的一种注解。

因 为 SpringBoot 本质上就是一个 spring 应用,所以通过这 个注解来加载 IOC 容器的配置是很正常的。所以在启动类里面标注了@Configuration,意味着它其实也是一个 IoC 容器的配置类。

传统意义上的 spring 应用都是基于xml 形式来配置 bean 的依赖关系,然后在spring 容器启动的时候,把 bean 进行初始化,如果 bean 之间存在依赖关系,则分析这些已经在 IoC 容器中的 bean 根据依赖关系进行组装。

直到 Java5 中,引入了 Annotations 这个特性,Spring 框架也紧随大流并且推出了基于 Java 代码和 Annotation 元 信息的依赖关系绑定描述的方式,也就是 JavaConfig。

从 spring3 开始,spring 就支持了两种 bean 的配置方式,一种是基于 xml 文件方式、另一种就是 JavaConfig。 任何一个标注了@Configuration 的 Java 类定义都是一个 JavaConfig 配置类。

在这个配置类中,任何标注了 @Bean 的方法,它的返回值都会作为 Bean 定义注册到 Spring 的 IOC 容器,方法名默认成为这个 bean 的 id。

简单分析@ComponentScan

@ComponentScan 这个注解是大家接触得最多的了,相当于xml 配置文件中的
<context:component-scan>。

它的主要作用就是扫描指定路径下的标识了需要装配的类,自动装配到 spring 的 Ioc 容器中。 标识需要装配的类的形式主要是:@Component、 @Repository、@Service、@Controller这类的注解标识的类。

ComponentScan默认会扫描当前 package 下的的所有加了相关注解标识的类到 IoC 容器中;

深入分析@EnableAutoConfiguration

我们把@EnableAutoConfiguration 放在最后讲的目的并不是说它是一个新的东西,只是他对于springboot来说意义重大。

EnableAutoConfiguration的主要作用其实就是帮助springboot应用把所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器中。

Enable并不是新鲜玩意

在spring3.1版本中,提供了一系列@Enable 开头的注解,Enable注解应该是在 JavaConfig 框架上更进一步的完善,用户在使用 spring 相关的框架时,避免配置大量的代码从而降低使用难度。

比如常见的一些Enable注解:@EnableWebMvc,(这个注解引入了MVC框架在Spring应用中需要用到的所有bean); 比如说@EnableScheduling,开启计划任务的支持。

找到 EnableAutoConfiguration,我们可以看到每一个涉及到Enable开头的注解,都会带有一个@Import 的注解。

@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

@Import注解

import注解是什么意思呢? 联想到 xml 形式下有一个 形式的注解,就明白它的作用了。 import 就是把多个分来的容器配置合并在一个配置中。在 JavaConfig 中所表达的意义是一样的。

@Import 注解可以配置三种不同的class

基于普通bean或者带有@Configuration的bean进行注入;
实现 ImportSelector 接口进行动态注入;
实现 ImportBeanDefinitionRegistrar 接口进行动态注入;
分析@Import(AutoConfigurationImportSelector.class)
了解ImportSelector和ImportBeanDefinitionRegistrar 后,对于EnableAutoConfiguration的理解就容易一些了,它会通过 import 导入第三方提供的 bean 的配置类: @Import(AutoConfigurationImportSelector.class)

从名字来看,可以猜到它是基于 ImportSelector 来实现基于动态 bean 的加载功能。

之前我们讲过 Springboot @Enable*注解的工作原理 ImportSelector接口selectImports 返回的数组(类的全类名)都会被纳入到spring容器中。 那么可以猜想到这里的实现原理也一定是一样的,定位到AutoConfigurationImportSelector这个类中的selectImports方法。

1 public String[] selectImports(AnnotationMetadata annotationMetadata) {
2         if (!this.isEnabled(annotationMetadata)) {
3             return NO_IMPORTS;
4         } else {
5             AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
6             AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
7             return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
8         }
9     }


定位到 AutoConfigurationMetadataLoader.loadMetadata代码,可以看到加载了"META-INF/spring-autoconfigure-metadata.properties";

 
1 final class AutoConfigurationMetadataLoader {
2 protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";
3 
4 private AutoConfigurationMetadataLoader() {
5 }
6 
7 public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
8     return loadMetadata(classLoader, PATH);
9 }

定位到this.getAutoConfigurationEntry->getCandidateConfigurations,可以看到用到SpringFactoriesLoader;

1 protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
2      List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
3              getBeanClassLoader());
4      Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
5              + "are using a custom packaging, make sure that file is correct.");
6      return configurations;
7  }

本质上来说,其实EnableAutoConfiguration会帮助springboot应用把所有符合@Configuration配置都加载到当前SpringBoot创建的IoC容器,而这里面借助了Spring框架提供的一个工具类SpringFactoriesLoader的支持,以及用到了Spring提供的条件注解 @Conditional,选择性的针对需要加载的bean进行条件过滤

SpringFactoriesLoader

SpringFactoriesLoader的作用是从 classpath/META-INF/spring.factories 文件中,根据 key 来加载对应的类到spring IoC容器中。

定位到SpringFactoriesLoader.loadSpringFactories源码,可以看到加载了"META-INF/spring.factories"。

1 private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
2         MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
3         if (result != null) {
4             return result;
5         } else {
6             try {
7                 Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
8                 LinkedMultiValueMap result = new LinkedMultiValueMap();

它其实和java中的SPI机制的原理是一样的,不过它比SPI更好的点在于不会一次性加载所有的类,而是根据 key进行加载。

例如:dubbo-spring-boot-starter-2.0.0.jar!/META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.dubbo.spring.boot.DubboAutoConfiguration,\
com.alibaba.dubbo.spring.boot.DubboProviderAutoConfiguration,\
com.alibaba.dubbo.spring.boot.DubboConsumerAutoConfiguration

条件过滤Conditional

在分析AutoConfigurationImportSelector的源码时看到,会先扫描 spring-autoconfiguration-metadata.properties 文件,最后再扫描spring.factories对应的类时,会结合前面的元数据进行过滤,为什么要过滤呢? 原因是很多的@Configuration 其实是依托于其他的框架来加载的,如果当前的classpath环境下没有相关联的依赖,则意味着这些类没必要进行加载,所以,通过这种条件过滤可以有效的减少@configuration类的数量从而降低 SpringBoot 的启动时间。

猜你喜欢

转载自www.cnblogs.com/xingxin666/p/11625566.html