1. 程式人生 > 其它 >SpringBoot啟動原理解析

SpringBoot啟動原理解析

new PackageImports(metadata).getPackageNames()

一.前言

SpringBoot專案的啟動, 都需要如下的啟動類

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

分析啟動類, 可以看出核心是:

  • 註解@SpringBootApplication
  • 方法SpringApplication.run(SpringBootDemo3Application.class, args)

分析SpringBoot的啟動原理, 就需要從以上兩處開始

二.註解@SpringBootApplication

點開註解, 原始碼如下

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {

}

@SpringBootApplication是由三個註解組合而成( 原生JDK註解忽略 ), 分別是:

  • @SpringBootConfiguration
  • @EnableAutoConfiguration
  • @ComponentScan

即以上三個註解可以替代@SpringBootApplication

2.1 註解@SpringBootConfiguration

首先檢視該註解原始碼

@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

該註解包含:

  • @configuration : 該註解是Spring原生註解, 標註該類是Spring配置類, 可以在該類中向IOC容器新增Bean
  • @Indexed : 標識專案在編譯打包的時候會在專案中自動生成META-INT/spring.components檔案, 當SpringContext在執行ComponentScan掃描時,META-INT/spring.components將會被CandidateComponentsIndexLoader讀取並載入,轉換為CandidateComponentsIndex物件,這樣的話@ComponentScan不在掃描指定的package,而是讀取CandidateComponentsIndex物件,從而達到提升效能的目的
  • proxyBeanMethods() default true : 外部無論對配置類中的這個元件註冊方法呼叫多少次獲取的都是之前註冊容器中的單例項物件

2.2 註解@ComponentScan

作用 : @ComponentScan的功能其實就是自動掃描並載入符合條件的元件(比如@Component和@Repository等)或者bean定義,最終將這些bean定義載入到IoC容器中

2.3 註解@EnableAutoConfiguration

作用 : 該註解是SpringBoot的核心註解, 藉助@Import的幫助,將所有符合自動配置條件的bean定義載入到IoC容器

@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

該註解包含

  • @AutoConfigurationPackage
  • @Import({AutoConfigurationImportSelector.class})

2.3.1 註解@AutoConfigurationPackage

檢視原始碼, 該註解中包含@Import(AutoConfigurationPackages.Register.class) , 通過@Import註解將Register匯入到ioc容器中, 然後通過Register向IOC容器中匯入一系列元件

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

   /**
    * Base packages that should be registered with {@link AutoConfigurationPackages}.
    * <p>
    * Use {@link #basePackageClasses} for a type-safe alternative to String-based package
    * names.
    * @return the back package names
    * @since 2.3.0
    */
   String[] basePackages() default {};

   /**
    * Type-safe alternative to {@link #basePackages} for specifying the packages to be
    * registered with {@link AutoConfigurationPackages}.
    * <p>
    * Consider creating a special no-op marker class or interface in each package that
    * serves no purpose other than being referenced by this attribute.
    * @return the base package classes
    * @since 2.3.0
    */
   Class<?>[] basePackageClasses() default {};

}
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

   @Override
   public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
      register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
   }

   @Override
   public Set<Object> determineImports(AnnotationMetadata metadata) {
      return Collections.singleton(new PackageImports(metadata));
   }

}
  • AnnotationMetadata metadata : 啟動類
  • new packageImports(metadata).getPackageNames : 獲得啟動類的包名
  • BeanDefinitionRegistry registry
  • register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0])); : 將啟動類的包( 包含子包 )所有標註了@Component相關注解的元件匯入到IOC容器

2.3.2 @Import({AutoConfigurationImportSelector.class})

將AutoConfigurationImportSelector

  • 利用getAutoConfigurationEntry(annotationMetadata);給容器中批量匯入一些元件
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
	if (!isEnabled(annotationMetadata)) {
		return NO_IMPORTS;
	}
	AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
	return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}
  • 呼叫List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)獲取到所有需要匯入到容器中的配置類
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;
}
  • 利用工廠載入 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的元件
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
	ClassLoader classLoaderToUse = classLoader;
	if (classLoaderToUse == null) {
		classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
	}
	String factoryTypeName = factoryType.getName();
	return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
  • META-INF/spring.factories位置來載入一個檔案。 預設掃描我們當前系統裡面所有META-INF/spring.factories位置的檔案 spring-boot-autoconfigure-2.3.4.RELEASE.jar包裡面也有META-INF/spring.factories

2.4 註解小結