SpringBoot自动装配机制的原理分析

2,910 阅读7分钟

什么是 springboot

​ 对于spring框架,我们接触得比较多的应该是spring mvc和 spring。而 spring 的核心在于 IOC(控制反转)和 DI (依赖注入)。而这些框架在使用的过程中会需要配置大量 的xml,或者需要做很多繁琐的配置。 springboot 框架是为了能够帮助使用 spring 框架的开发者快速高效的构建一个基于 spirng 框架以及 spring 生态 体系的应用解决方案。它是对约定优于配置这个理念下 的一个最佳实践。因此它是一个服务于框架的框架,服务 的范围是简化配置文件。

约定优于配置的体现

  1. maven的目录结构
    • 默认有resources文件夹存放配置文件
    • 默认打包方式为jar
  2. spring-boot-starter-web 中默认包含 spring mvc 相关依赖以及内置的tomcat容器,使得构建一个web应用 更加简单
  3. 默认提供application.properties/yml文件
  4. 默认通过 spring.profiles.active 属性来决定运行环境时读取的配置文件
  5. EnableAutoConfiguration 默认对于依赖的starter进行自动装载

启动原理

我们开发任何一个Spring Boot项目,都会用到如下的启动类

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

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

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

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

  1. @Configuration (@SpringBootConfiguration里面也用的@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

Enable 并不是新鲜玩意

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

比如@EnableScheduling、@EnableCaching、@EnableWebMvc等,@EnableAutoConfiguration的理念和做事方式其实一脉相承,简单概括一下就是,借助@Import的支持,收集和注册特定场景相关的bean定义。

  • @EnableWebMvc,这个注解引入了MVC 框架在Spring 应用中需要用到的所有 bean
  • @EnableScheduling是通过@Import将Spring调度框架相关的bean定义都加载到IoC容器,开启计划任务的支持。

而@EnableAutoConfiguration也是借助@Import的帮助,将所有符合条件的@Configuration 配置都加载到当前SpringBoot创建并使用的IoC容器中。仅此而已!

@EnableAutoConfiguration会根据类路径中的jar依赖为项目进行自动配置,如:添加了spring-boot-starter-web依赖,会自动添加Tomcat和Spring MVC的依赖,Spring Boot会对Tomcat和Spring MVC进行自动配置。

@EnableAutoConfiguration作为一个复合Annotation,其自身定义关键信息如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

@Import(AutoConfigurationImportSelector.class)从名字来看,可以猜到它是基于ImportSelector来实现基于动态bean的加载功能。要知道Springboot @Enable*注解的工作原理ImportSelector接口selectImports返回的数组(类的全类名)都会被纳入到 spring容器中。 那么可以猜想到这里的实现原理也一定是一样的,定位到 AutoConfigurationImportSelector这个类中的 selectImports方法 。

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

AutoConfigurationImportSelector

我们来看下AutoConfigurationImportSelector源码下的selectImports方法

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
   }
    //加载spring-autoconfigure-metadata.properties配置文件中的数据
   AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
         .loadMetadata(this.beanClassLoader);
    //通过getAutoConfigurationEntry获取需要动态加载的class
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
         annotationMetadata);
    //返回需要交给spring进行注入的类
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

AutoConfigurationMetadataLoader 的源码:

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

   static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
      try {
         Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
               : ClassLoader.getSystemResources(path);
         Properties properties = new Properties();
         while (urls.hasMoreElements()) {
            properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
         }
         return loadMetadata(properties);
      }
      catch (IOException ex) {
         throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
      }
   }

上述selectImports方法就返回了需要springboot自动装配的一些bean,通过String[]的形式返回需要装配的bean的name,但是这个方法的在真正返回需要装配的bean的name之前,还做了很多操作。做了些动态过滤的操作。

第一步是通过loadMetadata加载当前classpath下的spring-autoconfigure-metadata.properties文件,这个文件里面配置了所有动态加载的条件。第二步是通过getAutoConfigurationEntry获取需要动态加载的class,这一步具体源码如下:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
      AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
    //下面这一步是去加载classpath下的spring.factories文件中的实例
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
   configurations = removeDuplicates(configurations);
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   configurations.removeAll(exclusions);
   configurations = filter(configurations, autoConfigurationMetadata);
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

getCandidateConfigurations这个详细源码如下:

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

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

可以看出这里就是加载当前classpath下的所有的spring.factories文件中的内容。下面就是spring.factories中EnableAutoConfiguration的配置。这些如果没有AutoConfigurationImportSelector的过滤操作,这里所有配置的值,都会在getCandidateConfigurations方法中会返回给IOC容器,springboot会自动装载这些类。

总结

@EnableAutoConfiguration作用就是从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。这些功能配置类要生效的话,会去classpath中找是否有该类的依赖类(也就是pom.xml必须有对应功能的jar包才行)并且配置类里面注入了默认属性值类,功能类可以引用并赋默认值。生成功能类的原则是自定义优先,没有自定义时才会使用自动装配类。