@SpringBootApplication
@SpringBootApplication
注解了 spring boot 应用的入口,我们从这个注解入手整理 spring boot 的启动过程。
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
@SpringBootApplication
本身是一个组合注解,其中包含的最重要的注解有 3 个:
@SpringBootConfiguration
这个注解中包含了@Configuration
注解,表示当前类用于 bean 的配置,将被自动的加入容器中。@ComponentScan
可以使用这个注解的属性让容器将其他的配置类加入。@EnableAutoConfiguration
前两个类在普通的 spring 应用中也会出现,不同的是这第三个注解,它将自动导入应用需要的类,在容器中装配。
@EnableAutoConfiguration
由引入了两个注解:
@AutoConfigurationPackage
通过这个注解,引入用于实现自动注册的注册中心的类:AutoConfigurationPackages.Registrar.class
。- 引入(这个注解并非直接在
@EnableAutoConfiguration
上,而是通过@Import
引入)AutoConfigurationImportSelector.class
。这个注解通过实现DeferredImportSelector
而实现了ImportSelector
中的selectImports
方法。这个方法的定义如下:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
这个方法在容器初始化 bean 之前执行,用于从各个组件的 jar 包中返回需要加载的类。再由类加载器加载到 jvm 中。
下面是 getAutoConfigurationEntry
方法代码:
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
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 = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
其中 AutoConfigurationEntry
的定义如下:
protected static class AutoConfigurationEntry {
private final List<String> configurations;
private final Set<String> exclusions;
private AutoConfigurationEntry() {
this.configurations = Collections.emptyList();
this.exclusions = Collections.emptySet();
}
/**
* Create an entry with the configurations that were contributed and their
* exclusions.
* @param configurations the configurations that should be imported
* @param exclusions the exclusions that were applied to the original list
*/
AutoConfigurationEntry(Collection<String> configurations, Collection<String> exclusions) {
this.configurations = new ArrayList<>(configurations);
this.exclusions = new HashSet<>(exclusions);
}
public List<String> getConfigurations() {
return this.configurations;
}
public Set<String> getExclusions() {
return this.exclusions;
}
}
上面的方法主要是做了加载组件中的配置列表(也完成了多个组件的去重工作),同时加载组件中被排除的类。
在过程中使用了以下的一个方法:
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.loadFactoryNames
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
自动配置器会跟根据传入的 factoryType.getName()
到项目系统路径下所有的 spring.factories 文件中找到相应类型的 factory 的实现类,从而加载。
spring-boot-starter-xxx
selectImports
会将组件需要的类加载到 jvm 中,而 spring-boot-starter-xxx 则包含了这些组件。(如果你尝试去打开过 starter 依赖,一般来说,你会发现它实际上只有一个 .pom 文件,甚至没有一个 .class 和 .java)
SpringFactoriesLoader
在 @SpringBootApplication
的部分提供过这个类,这是一个工厂类的加载器,其中最重要的方法是 loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader)
和 loadFactories(Class<T> factoryClass, ClassLoader classLoader)
。前者的参数为工厂方法的父类或接口,和一个类加载器,从这个类加载器的路径下(类加载器有相应的负责的类路径)下搜索给定工厂类的子类;后者调用前者,并根据前者的返回值初始化这些类。
配合 @EnableAutoConfiguration
使用的话,它更多是提供一种配置查找的功能支持,即根据 @EnableAutoConfiguration
的完整类名 org.springframework.boot.autoconfigure.EnableAutoConfiguration
作为查找的 key,获取对应的一组 @Configuration
类,作为需要的组件的配置。
实例化 SpringApplication
启动 spring 应用的 run()
方法几经调用:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
在真正运行 run()
方法时会先实例化 SpringApplication
对象。
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param resourceLoader the resource loader to use
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #setSources(Set)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
在这个构造中主要干了 4 件事:
- 判断当前程序的类型:
WebApplicationType.deduceFromClasspath()
根据类路径中是否存在某个特征的类,有NONE
,SERVLET
,REACTIVE
等类型。 - 获取工厂实例(即之前利用
SpringFactoriesLoader
加载的实例)。 - 实例化监听器(方式同上)。
- 配置应用主方法所在的类。
run 方法
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
run 方法大致可分为:
- 收集各种条件和回调接口
完成后发出started()
通知 - 创建并配置
Environment
完成后发出environmentPrepared()
通知 - 创建并初始化
ApplicationContext
并设置Environment
加载配置
在各自完成后依次发出contextPrapared
和contextLoaded()
通知 - 刷新(refresh)
ApplicationContext
完成后执行CommnadLineRunner
并通知finished()