自动配置流程图

Spring Boot 源码之自动配置原理解析 - 图1

源码解析

Spring Boot 应用某个类上标注@SpringBootApplication说明这个类是主配置类,需要运行这个类的 main 方法来启动应用:

  1. // 设置当前注解可以标记在哪里
  2. @Target(ElementType.TYPE)
  3. // 当注解标注的类编译时以什么方式保留:RUNTIME-被 jvm 加载
  4. @Retention(RetentionPolicy.RUNTIME)
  5. // java doc 会生成注解信息
  6. @Documented
  7. // 是否会被继承
  8. @Inherited
  9. // 标注在某个类上,表示这是一个 Spring Boot 的配置类
  10. @SpringBootConfiguration
  11. // 开启自动配置功能
  12. @EnableAutoConfiguration
  13. // 扫描包
  14. @ComponentScan(excludeFilters = {
  15. @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
  16. @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
  17. public @interface SpringBootApplication {

@EnableAutoConfiguration

开启自动配置的注解

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. // 将当前配置类所在包保存在 BasePackages 的 Bean 中,供 Spring 内部使用
  6. @AutoConfigurationPackage
  7. @Import(AutoConfigurationImportSelector.class)
  8. public @interface EnableAutoConfiguration {
  9. // …
  10. }

该注解通过@SpringBootApplication被间接的标记在了 Spring Boot 的启动类上;在 SpringApplication.run(…)的内部就会执行 selectImports()方法,找到所有 JavaConfig 自动配置类的全限定名对应的 class,然后将所有自动配置类加载到 Spring 容器中

@AutoConfigurationPackage

// 保存扫描路径,提供给spring-data-jpa 需要扫描 @Entity
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

@Import(AutoConfigurationImportSelector.class)关键点

@EnableAutoConfiguration注解内使用@Import注解来完成导入配置的功能,而AutoConfigurationImportSelector实现了DeferredImportSelectorSpring内部再解析@Import注解时会调用getAutoConfigurationEntry方法

// 扫描具有 META-INF/spring.factories 文件的 jar 包
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   // 从META-INF/spring.factories中获得候选的自动配置类
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
   // 排重
   configurations = removeDuplicates(configurations);
   //根据EnableAutoConfiguration注解中属性,获取不需要自动装配的类名单
   Set<String> exclusions = getExclusions(annotationMetadata, attributes); 
   // 根据:@EnableAutoConfiguration.exclude
   // @EnableAutoConfiguration.excludeName
   // spring.autoconfigure.exclude  进行排除
   checkExcludedClasses(configurations, exclusions);
   // exclusions 也排除
   configurations.removeAll(exclusions);
   // 通过读取spring.factories 中的OnBeanCondition\OnClassCondition\OnWebApplicationCondition进行过滤
   configurations = getConfigurationClassFilter().filter(configurations);
   // 这个方法是调用实现了AutoConfigurationImportListener  的bean..  分别把候选的配置名单,和排除的配置名单传进去做扩展
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

任何一个 SpringBoot 应用都会引入 spring-boot-autoconfigure,而 spring.factories 文件就在该包下面;
spring.factories 文件是 key=value 形式,多个 value 使用’ , ‘隔开;该文件中定义了关于初始化、监听器等信息,而真正使自动配置生效的 key 是 org.springframework.boot.autoconfigure.EnableAutoConfiguration

自动装配例子(Http 编码自动装配)

// 标记了@Configuration Spring 底层会给配置创建 cglib 动态代理;作用:防止每次调用本类的 Bean 方法而重新创建对象,Bean 默认是单例
@Configuration(proxyBeanMethods = false)

// 启用可以在配置类设置的属性对应的类
@EnableConfigurationProperties(ServerProperties.class)

// @Conditional 派生注解:必须是@Conditional 指定的条件成立,才给容器中添加组件,配置的内容才生效
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {

   private final Encoding properties;

   public HttpEncodingAutoConfiguration(ServerProperties properties) {
      this.properties = properties.getServlet().getEncoding();
   }

   @Bean
   @ConditionalOnMissingBean
   public CharacterEncodingFilter characterEncodingFilter() {
      CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
      filter.setEncoding(this.properties.getCharset().name());
      filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
      filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
      return filter;
   }