SpringBoot核心原理之自动配置AutoConfiguration

  • Post author:
  • Post category:其他




从@SpringBootApplication注解入手

为了揭开SpringBoot的奥秘,我们直接从Annotation入手,看看@SpringBootApplication里面,做了什么?

打开@SpringBootApplication这个注解,可以看到它实际上是一个复合注解

@Target(ElementType.TYPE) 
@Retention(RetentionPolicy.RUNTIME) 
@Documented
@Inherited
@SpringBootConfiguration //实际上是 @Configuration 
@EnableAutoConfiguration 
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication { 

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

  1. @Configuration
  2. @EnableAutoConfiguration
  3. @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

  1. 基于普通bean或者带有@Configuration的bean进行注入;
  2. 实现 ImportSelector 接口进行动态注入;
  3. 实现 ImportBeanDefinitionRegistrar 接口进行动态注入;


分析@Import(AutoConfigurationImportSelector.class)

了解ImportSelector和ImportBeanDefinitionRegistrar 后,对于EnableAutoConfiguration的理解就容易一些了,它会通过 import 导入第三方提供的 bean 的配置类: @Import(AutoConfigurationImportSelector.class)

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

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

AutoConfigurationImportSelector

这个类中的

selectImports

方法。

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

定位到

AutoConfigurationMetadataLoader

.

loadMetadata

代码,可以看到加载了”META-INF/

spring-autoconfigure-metadata.properties

“;

final class AutoConfigurationMetadataLoader {

  protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";

  private AutoConfigurationMetadataLoader() {
  }

  public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
  	return loadMetadata(classLoader, PATH);
  }

定位到this.getAutoConfigurationEntry->

getCandidateConfigurations

,可以看到用到

SpringFactoriesLoader

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

本质上来说,其实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

“。

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 {
                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 factoryClassName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

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

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

它其实和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 的启动时间。



Conditional的注解
Conditions 描述
@ConditionalOnBean 在存在某个 bean 的时候
@ConditionalOnMissingBean 不存在某个 bean 的时候
@ConditionalOnClass 当前 classpath 可以找到某个类型的类时
@ConditionalOnMissingClass 当前 classpath 不可以找到某个类型的类时
@ConditionalOnResource 当前 classpath 是否存在某个资源文件
@ConditionalOnProperty 当前 jvm 是否包含某个系统属性为某个值
@ConditionalOnWebApplication 当前 spring context 是否是 web 应用程序


注:本文整理自咕泡学院Mic老师的课堂笔记。



版权声明:本文为xingxincsdn原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。