Углубленный принцип SpringBoot и анализ исходного кода

Spring Boot

Углубленный принцип SpringBoot и анализ исходного кода

Управление зависимостями SpringBoot

Проект SpringBoot косвенно наследует spring-boot-dependencies, что реализует унифицированное управление версиями для общих технических фреймворков, поэтому файлы зависимостей, управляемые spring-boot-dependencies в pom.xml проекта SpringBoot, не нужно помечать номером версии файла зависимостей. Внедрение стартера позволяет реализовать разработку соответствующей сцены без необходимости импорта дополнительных зависимых файлов.

Автоматическая настройка (запуск процесса)

Запись запуска приложения SpringBoot:@SpringBootApplicationАннотация в классе аннотацийmain()метод,@SpringBootApplicationВозможность сканирования компонентов Spring и автоматической настройки SpringBoot.

@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 {
...
}

1. Аннотация @SpringBootConfiguration

@SpringBootConfigurationАннотация представляет класс конфигурации Spring Boot, и аннотация предназначена только для@ConfigurationПростая инкапсуляция аннотаций с@ConfigurationАннотации работают так же.

2. Аннотация @EnableAutoConfiguration

@EnableAutoConfigurationОбратите внимание, что функция автоматической настройки включена.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 自动配置包
@AutoConfigurationPackage
// 自动配置类扫描导入
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
	Class<?>[] exclude() default {};
    String[] excludeName() default {};
}
  • @AutoConfigurationPackageаннотация

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    // 导入 Registrar 组件类
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {
    }
    
    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    	// 这个方法是导入组件类的具体实现
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            // 将主程序类所在包及其子包下的组件扫描到 Spring 容器中
            register(registry, new PackageImport(metadata).getPackageName());
        }
    
        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImport(metadata));
        }
    }
    
    private static final class PackageImport {
        private final String packageName;
        PackageImport(AnnotationMetadata metadata) {
            // 获取注解包名
            this.packageName = ClassUtils.getPackageName(metadata.getClassName());
        }
        String getPackageName() {
            return this.packageName;
    	}
    }
    
  • @Import(AutoConfigurationImportSelector.class)

    будетAutoConfigurationImportSelectorЭтот класс импортируется в контейнер Spring,AutoConfigurationImportSelectorЭто может помочь приложению SpringBoot загрузить все подходящие конфигурации в контейнер IoC (ApplicationContext), созданный и используемый текущим SpringBoot.

    Через анализ исходного кода этот класс проходит черезselectImports()Этот метод сообщает SpringBoot, какие компоненты необходимо импортировать.

    // 这个方法告诉springboot都需要导入那些组件
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        //判断 enableautoconfiguration注解有没有开启,默认开启(是否进行自动装配)
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        //1. 加载配置文件META-INF/spring-autoconfigure-metadata.properties,从中获取所有支持自动配置类的条件
        //作用:SpringBoot使用一个Annotation的处理器来收集一些自动装配的条件,那么这些条件可以在META-INF/spring-autoconfigure-metadata.properties进行配置。
        // SpringBoot会将收集好的@Configuration进行一次过滤进而剔除不满足条件的配置类
        // 自动配置的类全名.条件=值
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
        
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
    
    • loadMetadata()

      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 {
                  //1.读取spring-boot-autoconfigure.jar包中spring-autoconfigure-metadata.properties的信息生成urls枚举对象
                  Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path) : ClassLoader.getSystemResources(path);
                  // 遍历 URL 数组,读取到 properties 中
                  Properties properties = new Properties();
      
                  //2.解析urls枚举对象中的信息封装成properties对象并加载
                  while (urls.hasMoreElements()) {
                      properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
                  }
                  
      			// 将 properties 转换成 PropertiesAutoConfigurationMetadata 对象
      			//根据封装好的properties对象生成AutoConfigurationMetadata对象返回
      			return loadMetadata(properties);
      		} catch (IOException ex) {
      			throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
      		}
      	}
      
      	static AutoConfigurationMetadata loadMetadata(Properties properties) {
      		return new PropertiesAutoConfigurationMetadata(properties);
      	}
      
      	/**
      	 * {@link AutoConfigurationMetadata} implementation backed by a properties file.
      	 */
      	private static class PropertiesAutoConfigurationMetadata implements AutoConfigurationMetadata {
      
              /**
               * Properties 对象
               */
      		private final Properties properties;
      
      		PropertiesAutoConfigurationMetadata(Properties properties) {
      			this.properties = properties;
      		}
      
      		@Override
      		public boolean wasProcessed(String className) {
      			return this.properties.containsKey(className);
      		}
      
      		@Override
      		public Integer getInteger(String className, String key) {
      			return getInteger(className, key, null);
      		}
      
      		@Override
      		public Integer getInteger(String className, String key, Integer defaultValue) {
      			String value = get(className, key);
      			return (value != null) ? Integer.valueOf(value) : defaultValue;
      		}
      
      		@Override
      		public Set<String> getSet(String className, String key) {
      			return getSet(className, key, null);
      		}
      
      		@Override
      		public Set<String> getSet(String className, String key, Set<String> defaultValue) {
      			String value = get(className, key);
      			return (value != null) ? StringUtils.commaDelimitedListToSet(value) : defaultValue;
      		}
      
      		@Override
      		public String get(String className, String key) {
      			return get(className, key, null);
      		}
      
      		@Override
      		public String get(String className, String key, String defaultValue) {
      			String value = this.properties.getProperty(className + "." + key);
      			return (value != null) ? value : defaultValue;
      		}
      	}
      }
      
    • getAutoConfigurationEntry()

      protected AutoConfigurationEntry getAutoConfigurationEntry( AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
          // 1. 判断是否开启注解。如未开启,返回空串
          if (!isEnabled(annotationMetadata)) {
              return EMPTY_ENTRY;
          }
          // 2. 获得注解的属性
          AnnotationAttributes attributes = getAttributes(annotationMetadata);
      
          // 3. getCandidateConfigurations()用来获取默认支持的自动配置类名列表
          // spring Boot在启动的时候,使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories,
          // 找出其中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的属性定义的工厂类名称,
          // 将这些值作为自动配置类导入到容器中,自动配置类就生效了
          List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
      
      
          // 3.1 //去除重复的配置类,若我们自己写的starter 可能存在重复的
          configurations = removeDuplicates(configurations);
          // 4. 如果项目中某些自动配置类,我们不希望其自动配置,我们可以通过EnableAutoConfiguration的exclude或excludeName属性进行配置,
          // 或者也可以在配置文件里通过配置项“spring.autoconfigure.exclude”进行配置。
          //找到不希望自动配置的配置类(根据EnableAutoConfiguration注解的一个exclusions属性)
          Set<String> exclusions = getExclusions(annotationMetadata, attributes);
          // 4.1 校验排除类(exclusions指定的类必须是自动配置类,否则抛出异常)
          checkExcludedClasses(configurations, exclusions);
          // 4.2 从 configurations 中,移除所有不希望自动配置的配置类
          configurations.removeAll(exclusions);
      
          // 5. 对所有候选的自动配置类进行筛选,根据项目pom.xml文件中加入的依赖文件筛选出最终符合当前项目运行环境对应的自动配置类
      
          //@ConditionalOnClass : 某个class位于类路径上,才会实例化这个Bean。
          //@ConditionalOnMissingClass : classpath中不存在该类时起效
          //@ConditionalOnBean : DI容器中存在该类型Bean时起效
          //@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
          //@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
          //@ConditionalOnExpression : SpEL表达式结果为true时
          //@ConditionalOnProperty : 参数设置或者值一致时起效
          //@ConditionalOnResource : 指定的文件存在时起效
          //@ConditionalOnJndi : 指定的JNDI存在时起效
          //@ConditionalOnJava : 指定的Java版本存在时起效
          //@ConditionalOnWebApplication : Web应用环境下起效
          //@ConditionalOnNotWebApplication : 非Web应用环境下起效
      
          //总结一下判断是否要加载某个类的两种方式:
          //根据spring-autoconfigure-metadata.properties进行判断。
          //要判断@Conditional是否满足
          // 如@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })表示需要在类路径中存在SqlSessionFactory.class、SqlSessionFactoryBean.class这两个类才能完成自动注册。
          configurations = filter(configurations, autoConfigurationMetadata);
      
      
          // 6. 将自动配置导入事件通知监听器
          //当AutoConfigurationImportSelector过滤完成后会自动加载类路径下Jar包中META-INF/spring.factories文件中 AutoConfigurationImportListener的实现类,
          // 并触发fireAutoConfigurationImportEvents事件。
          fireAutoConfigurationImportEvents(configurations, exclusions);
          // 7. 创建 AutoConfigurationEntry 对象
          return new AutoConfigurationEntry(configurations, exclusions);
      }
      
      • getCandidateConfigurations()

        protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
            // 让SpringFactoryLoader去加载一些组件的名字
            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;
        }
        
        • loadFactoryNames()

          public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
              String factoryClassName = factoryClass.getName();
              return loadSpringFactories(classLoader).getOrDefault( factoryClassName, Collections.emptyList());
          }
          
          	private static Map<String, List<String>> loadSpringFactories( @Nullable ClassLoader classLoader) {
                  MultiValueMap<String, String> result = cache.get(classLoader);
                  if (result != null) {
                      return result;
                  }
                  
                  try {
                      // 如果类加载器不为 null,则加载类路径下spring.factories,将其中设置的配置类的全路径信息封装为 Enumeration 类对象
                      // public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
                      Enumeration<URL> urls = (classLoader != null ?
          classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : 
          ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
          			result = new LinkedMultiValueMap<>();
                      // 循环 Enumeration 类对象,根据相应的节点信息生成 Properties 对象,通过传入的键获取值,在将值切割为一个个小的字符串转化为 Array,方法result集合中
          			while (urls.hasMoreElements()) {
                          URL url = urls.nextElement();
                          UrlResource resource = new UrlResource(url);
                          Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                          for (Map.Entry<?, ?> entry : properties.entrySet()) {
                              String factoryClassName = ((String) entry.getKey()).trim();
                              for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                                  result.add(factoryClassName, factoryName.trim());
          					}
          				}
          			}
          			cache.put(classLoader, result);
          			return result;
          		}
          		catch (IOException ex) {
          			throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
          		}
          	}
          

    @EnableAutoConfigurationзаключается в поиске по пути к классамMATE-INF/spring.factoriesконфигурационный файл и вставьте егоorg.springframework.boot.autoconfigure.EnableutoConfigurationСоответствующие элементы конфигурации воплощаются в соответствующие аннотации посредством отражения.@ConfigurationКласс конфигурации в виде JavaConfig загружается в контейнер IoC.

Суммировать:

Шаги для реализации автоматического подключения в нижней части Springboot:

  1. Запускается приложение Springboot;
  2. @SpringBootApplicationворваться;
  3. @EnableAutoConfiguration;
  4. @AutoConfigurationPackage:Эта комбинированная аннотация в основном@Import(AutoConfigurationPackages.Registrar.class), оно используетRegistrarкласс импортируется в контейнер, аRegistrarРоль класса состоит в том, чтобы сканировать основной каталог одноуровневого класса конфигурации и его подпакеты и импортировать соответствующие компоненты в контейнер управления созданием Springboot.
  5. @Import(AutoConfigurationImportSelector.class):это, поставивAutoConfigurationImportSelectorкласс импортируется в контейнер,AutoConfigurationImportSelectorГрупповой иск осуществляется черезselectImportsМетод хочет использовать только внутренний класс инструментаSpringFactoriesLoaderнайтиclasspathв упаковке jar, использованной вышеMATE-INF/spring.factoriesЗагрузка, реализует информацию о классе конфигурации вSpringFactoryЗагрузчик выполняет ряд процессов создания контейнера.

3. Аннотация @ComponentScan

@ComponentScanАннотация Корневой путь конкретного сканируемого пакета определяется расположением пакета, в котором находится основной класс запуска программы проекта Spring Boot.@AutoConfigurationPackageАннотация анализируется, чтобы получить конкретное расположение пакета, в котором находится основной класс запуска программы проекта Springboot.

Суммировать:

|- @SpringBootConfiguration
	|- @Configuration //通过javaConfig的方式来添加组件到 IoC 容器中
|- @EnableAutoConfiguration
    |- @AutoConfigurationPackage //自动配置包,与@ComponentScan扫描到的添加到IOC
    |- @Import(AutoConfigurationImportSelector.class) //到META-INF/spring.factories中定义的bean 添加到 IoC 容器中
|- @ComponentScan //包扫描

Пользовательский стартер

Starter позволяет разработчикам, использующим определенную функцию, не обращать внимания на обработку различных зависимых библиотек, и не нуждаться в конкретной информации о конфигурации.Springboot автоматически находит нужные бины через классы в пути к классам, и вплетает соответствующие бины.

шаг

  1. Самостоятельный проект, названныйxxx-spring-boot-starter, Импортироватьspring-boot-autoconfigureполагаться

  2. Напишите класс конфигурации

    • @Configuration
    • @ConditionalOnXxx: указывает условия автоматической настройки
  3. Создано в рамках ресурсов/META-INF/spring.factories, настройте пользовательский класс конфигурации в этом файле

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.test.config.MyAutoConfiguration
    
  4. тестовое использование

Принцип выполнения SpringApplication.run()

исходный код

//调用静态类,参数对应的就是SpringbootDemoApplication.class以及main方法中的args
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class<?>[] { primarySource }, args);
}

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    //SpringApplication的启动由两部分组成:
    //1. 实例化SpringApplication对象
    //2. run(args):调用run方法
    return new SpringApplication(primarySources).run(args);
}

1. Инициализация и создание экземпляра SpringApplication

public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
}

@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.sources = new LinkedHashSet();
    // Banner 模式
    this.bannerMode = Mode.CONSOLE;
    this.logStartupInfo = true;
    // 是否添加 JVM 启动参数
    this.addCommandLineProperties = true;
    this.addConversionService = true;
    this.headless = true;
    this.registerShutdownHook = true;
    this.additionalProfiles = new HashSet();
    this.isCustomEnvironment = false;
    // 资源加载器
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");

    //项目启动类 SpringbootDemoApplication.class设置为属性存储起来
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

    //设置应用类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)
    // deduceFromClasspath()方法用于查看 ClassPath类路径下是否存在某个特征类,从而判断webApplicationType
    this.webApplicationType = WebApplicationType.deduceFromClasspath();

    // 设置初始化器(Initializer),最后会调用这些初始化器
    //所谓的初始化器就是org.springframework.context.ApplicationContextInitializer的实现类,在Spring上下文被刷新之前进行初始化的操作
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

    // 设置监听器(Listener)
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

    // 初始化 mainApplicationClass 属性:用于推断并设置项目main()方法启动的主程序启动类
    this.mainApplicationClass = deduceMainApplicationClass();
}

2. Старт инициализации проекта

public ConfigurableApplicationContext run(String... args) {
    // 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    // 初始化应用上下文和异常报告集合
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // 配置 headless 属性
    configureHeadlessProperty();

    //(1)获取并启动监听器
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        // 创建  ApplicationArguments 对象 初始化默认应用参数类
        // args是启动Spring应用的命令行参数,该参数可以在Spring应用中被访问。如:--server.port=9000
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

        //(2)项目运行环境Environment的预配置
        // 创建并配置当前SpringBoot应用将要使用的Environment
        // 并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        // 排除不需要的运行环境
        configureIgnoreBeanInfo(environment);
        // 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体
        Banner printedBanner = printBanner(environment);

        // (3)创建Spring容器
        // 根据 webApplicationType 进行判断,确定容器类型,如果为 SERVLET 类型,会反射创建相应的字节码,AnnotationConfigServletWebServerApplicationContext, 接着使用之前传世话设置的context、environment、listeners、applicationArgument 进行应用上下文的组装配置
        context = createApplicationContext();
        // 获得异常报告器 SpringBootExceptionReporter 数组
        //这一步的逻辑和实例化初始化器和监听器的一样,
        // 都是通过调用 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。
        exceptionReporters = getSpringFactoriesInstances(
            SpringBootExceptionReporter.class,
            new Class[] { ConfigurableApplicationContext.class }, context);


        // (4)Spring容器前置处理
        //这一步主要是在容器刷新之前的准备动作。包含一个非常关键的操作:将启动类注入容器,为后续开启自动化配置奠定基础。
        prepareContext(context, environment, listeners, applicationArguments,
                       printedBanner);

        // (5):刷新容器
        // 开启(刷新)Spring 容器,通过refresh方法对整个IoC容器的初始化(包括Bean资源的定位、解析、注册等等),同时向JVM运行时注册一个关机钩子,在JVM关机时关闭这个上下文,除非它当时已经关闭
        refreshContext(context);

        // (6):Spring容器后置处理
        //扩展接口,设计模式中的模板方法,默认为空实现。
        // 如果有自定义需求,可以重写该方法。比如打印一些启动结束log,或者一些其它后置处理
        afterRefresh(context, applicationArguments);
        // 停止 StopWatch 统计时长
        stopWatch.stop();
        // 打印 Spring Boot 启动的时长日志。
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        // (7)发出结束执行的事件通知
        listeners.started(context);

        // (8):执行Runners
        //用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序
        //Runner 运行器用于在服务启动时进行一些业务初始化操作,这些操作只在服务启动后执行一次。
        //Spring Boot提供了ApplicationRunner和CommandLineRunner两种服务接口
        callRunners(context, applicationArguments);
    } catch (Throwable ex) {
        // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    //   (9)发布应用上下文就绪事件
    //表示在前面一切初始化启动都没有问题的情况下,使用运行监听器SpringApplicationRunListener持续运行配置好的应用上下文ApplicationContext,
    // 这样整个Spring Boot项目就正式启动完成了。
    try {
        listeners.running(context);
    } catch (Throwable ex) {
        // 如果发生异常,则进行处理,并抛出 IllegalStateException 异常
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    //返回容器
    return context;
}