目录
SpringApplication.run(XXXApplication.class, args)
3:SpringBoot的ConfigurableEnvironment初始化
5:SpringBoot的prepareContext()准备IOC容器数据
SpringBoot在日常工作中带来了很多便利,通过application.properties即可替换默认属性值通过@SpringBootApplication和SpringApplication.run()即可完成Spring容器的启动,并自动为我们创建自动配置的bean,如下图,本文主要关注springboot启动时,自动创建过滤自动配置类、业务bean、加载资源文件的整体流程。
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实际上是一个组合注解,@Target(ElementType.TYPE) 、@Retention(RetentionPolicy.RUNTIME)、@Documented、@Inherited是java的一个元注解,@Target标志当前注解的使用类型是:类;枚举;接口,还是方法级、属性 等,@Retention是标志者当前注解的生效时期:运行期还是编译时。@Documented标志表明要记录的javadoc注释的类型和类似工具,@Inherited标志着当前注解是支持继承的。
@ComponentScan:扫描某个包下所有标志着的bean组件(Controller、Service、Component、Configuration等),excludeFilters:排除一些过滤器的实现,这个注解在后续启动流程中,会扫描SampleTomcatApplication同目录等级下的所有bean组件,但是并没有配置basePackages,猜想一:应该是后续根据run()中传入的java类,根据类的路径来扫描同目录下所有组件(后续说道)。
@SpringBootConfiguration:也是一个组件注解,没什么可看的,主要是使用@Configuration标志着当前注解有同样配置类注解的效果,使用该注解的类是配置类也会被加载到IOC容器中。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {// 省略}
@EnableAutoConfiguration:也是一个组合注解。java元注解忽略。主要是: @AutoConfigurationPackage:注解的作用是将 添加该注解的类所在的package 作为 自动配置package 进行管理,目的之一 就是在扫描同package下所有bean组件时,提供目标package。 @Import(AutoConfigurationImportSelector.class):将AutoConfigurationImportSelector类注入到Spring容器中,这个类在后续springboot启动时,通过SpringBoot的SPI机制加载并过滤自动配置类起到关键作用。
SpringApplication.run(XXXApplication.class, args)
上面注解说了下用途,具体代码执行逻辑,下面根据源码,一起来看下:
run()方法点进去,会调用下面这个方法,先是创建SpringApplication,然后执行该类的run()方法。
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
一个个来看:创建SpringbootApplication时,初始化类属性:设置primarySources为启动类class、设置web应用的类型:通常是servlet类型、根据SpringBoot的SPI机制初始化bootstrapRegistryInitializers、initializers、listeners;initializers、listeners主要是Springboot在初始化、启动流程中,初始化属性、发布springBoot的事件监听的作用。
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();
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// 初始化 initializers 属性
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 初始化 listeners 属性
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
关于SpringBoot的SPI机制 简单提下,有兴趣的朋友自行查阅资料深入。
SpringBoot启动时,会加载项目中依赖的jar包下resources下的META-INF的spring.factories中的属性,文件中通常是配置AutoConfiguration、Listeners、Initializers信息,通过SpringFactoriesLoader可以加载,并在特定流程中初始化bean,像springboot的初始化、监听器实现就是在创建SpringApplication类时就初始化了。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// 获取beantype的所有bean name,并创建bean实例。
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
// 加载出spring,factories中的配置类,根据type为key返回一个Map<String, List<String>>,并
// 记录到cache缓存中,getOrDefault()根据type去获取对应的List<String>。type如:
// EnableAutoConfiguration、ApplicationListener等等。
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());
}
下面看下创建好SpringApplication对象后,run()方法的后续流程 。
主要的流程是:
1、应用启动计时、配置headless 属性
2、获取springboot的事件监听,发送starting事件
3、创建springboot应用的environment,此时就会生成application.properties的propertySource
4、打印springboot的banner、 根据webApplicationType去创建IOC容器的类型
5、准备context上下文,初始化、load数据,发送相应事件监听。
6、通过refreshContext(),来加载springIOC的所有组件(spring的核心流程代码,本文不做过多解读)
7、IOC加载完成的后续操作,如:输出启动时长、发送启动成功事件、调用回调方法等整体流程就结束了。
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
// 创建DefaultBootstrapContext,将
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
// <2> 配置 headless 属性
configureHeadlessProperty();
// 获得 SpringApplicationRunListener 的数组,并启动监听(默认springboot提供了EventPublishingRunListener)
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 创建应用的environment,ConfigDataEnvironmentPostProcessor解析xml文件,生成propertySource,用于后续构建自动注入properties文件的属性填充
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();// 根据webApplicationType去创建IOC容器的类型
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
下面主要针对3、5、6步骤 进行Springboot的较详细的分析:
3:SpringBoot的ConfigurableEnvironment初始化
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment 根据webApplicationType返回相应默认的environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
// 监听构造environment的数据(application.xml),(通知 SpringApplicationRunListener 的数组,环境变量已经准备完成)
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
// 绑定 environment 到 SpringApplication 上
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = convertEnvironment(environment);
}
// 如果有 attach 到 environment 上的 MutablePropertySources ,则添加到 environment 的 PropertySource 中。
ConfigurationPropertySources.attach(environment);
return environment;
}
初始化environment时,listeners.environmentPrepared(bootstrapContext, environment);主要是对application.properties配置加载,发送 环境准备完成 事件后,
EnvironmentPostProcessorApplicationListener监听该事件,并进行处理,遍历所有的EnvironmentPostProcessor执行postProcessEnvironment来填充environment信息。
其中ConfigDataEnvironmentPostProcessor流程中createConfigDataLocationResolvers()并调用processAndApply()时,
通过SPI机制,load出处理properties、yml的class信息,作为ConfigDataEnvironment一个属性
,便于后续解析文件中的属性
processAndApply()方法在执行流程中,会通过ConfigDataImporter遍历出resource中的数据最终通过PropertiesPropertySourceLoader来解析出具体配置信息,见下图:
5:SpringBoot的prepareContext()准备IOC容器数据
主要是设置上下文的环境信息,调用ApplicationContextInitializer来初始化数据,发送contextPrepared监听,加载启动类注册到BeanDefinitionMap,发送contextLoaded监听。
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);// 设置springboot有关的context的属性
postProcessApplicationContext(context);
applyInitializers(context);// 部分数据初始化的操作,比如 contextId的 生成(ContextIdApplicationContextInitializer)
listeners.contextPrepared(context);
bootstrapContext.close(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);//启动IOC容器前发送监听
}
applyInitializers(context):主要功能:数据初始化或将类注册监听(例:ContextIdApplicationContextInitializer、ServerPortInfoApplicationContextInitializer、DelegatingApplicationContextInitializer)
// DelegatingApplicationContextInitializer
// 从环境信息中获取所有配置的需初始化bean,实例化,并调用其initialize方法
public void initialize(ConfigurableApplicationContext context) {
ConfigurableEnvironment environment = context.getEnvironment();
List<Class<?>> initializerClasses = getInitializerClasses(environment);
if (!initializerClasses.isEmpty()) {
applyInitializerClasses(context, initializerClasses);
}
}
// ContextIdApplicationContextInitializer
// SpringIterableConfigurationPropertySource的getConfigurationProperty()从配置文件中读取spring.application.name属性,未设置则默认application,写死的
public void initialize(ConfigurableApplicationContext applicationContext) {
ContextId contextId = getContextId(applicationContext);
applicationContext.setId(contextId.getId());
applicationContext.getBeanFactory().registerSingleton(ContextId.class.getName(), contextId);
}
//ServerPortInfoApplicationContextInitializer
// 将当前类注册到IOC的监听器中,当有事件发布时,刷新server.port属性
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.addApplicationListener(this);
}
6:refreshContext()
在该方法的invokeBeanFactoryPostProcessors(beanFactory)中,通过ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry()来注册bean实例。后续通过AutoConfigurationImportSelector(@import注解导入的类)来扫描过滤自动配置类。
/**
* 导入自动配置的核心方法(此处主要是操作自动配置的bean,非业务代码bean)
*
* Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
* of the importing {@link Configuration @Configuration} class.
* @param annotationMetadata the annotation metadata of the configuration class
* @return the auto-configurations that should be imported
*/
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// <1> 判断是否开启。如未开启,返回空数组
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// <2> 获得注解的属性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// <3> 获得符合条件的配置类的数组(SPI机制获取所有的EnableAutoConfiguration)
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// <3.1> 移除重复的配置类
configurations = removeDuplicates(configurations);
// <4> 获得需要排除的配置类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// <4.1> 校验排除的配置类是否合法
checkExcludedClasses(configurations, exclusions);
// <4.2> 从 configurations 中,移除需要排除的配置类
configurations.removeAll(exclusions);
// <5> 根据条件(Condition),过滤掉不符合条件的配置类(自动配置bean上的条件)
configurations = getConfigurationClassFilter().filter(configurations);
// <6> 触发自动配置类引入完成的事件
fireAutoConfigurationImportEvents(configurations, exclusions);
// <7> 创建 AutoConfigurationEntry 对象
return new AutoConfigurationEntry(configurations, exclusions);
}
通过SPI机制、反射来创建出OnBeanCondition、OnWebApplicationCondition、OnClassCondition,并作为属性来创建ConfigurationClassFilter,同时也会load出自动配置类及注解上增加的条件注解(例子:@ConditionalOnClass、@ConditionalOnBean等)。后续遍历ConfigurationClassFilter的这个属性,根据这三个filter来过滤出不符合的自动配置类,从加载出当前项目所需要的自动配置类。
// AutoConfigurationImportSelector
List<String> filter(List<String> configurations) {
long startTime = System.nanoTime();
String[] candidates = StringUtils.toStringArray(configurations);
boolean skipped = false;//通过class、webapplication、bean的配置,进行过滤自动加载的配置类
for (AutoConfigurationImportFilter filter : this.filters) {
// 模板方法实现,子类具体实现各自的match操作
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
if (!match[i]) {
candidates[i] = null;
skipped = true;
}
}
}
if (!skipped) {
return configurations;
}
List<String> result = new ArrayList<>(candidates.length);
for (String candidate : candidates) {
if (candidate != null) {
result.add(candidate);
}
}
if (logger.isTraceEnabled()) {
int numberFiltered = configurations.size() - result.size();
logger.trace("Filtered " + numberFiltered + " auto configuration class in "
+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
}
return result;
}
// FilteringSpringBootCondition父类定义通用代码,子类实现(getOutcomes()、getMatchOutcome())
public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
boolean[] match = new boolean[outcomes.length];
for (int i = 0; i < outcomes.length; i++) {
match[i] = (outcomes[i] == null || outcomes[i].isMatch());
if (!match[i] && outcomes[i] != null) {
logOutcome(autoConfigurationClasses[i], outcomes[i]);
if (report != null) {
report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
}
}
}
return match;
}
//OnClassCondition的实现,其他两种请见源码
private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end,
AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
for (int i = start; i < end; i++) {
String autoConfigurationClass = autoConfigurationClasses[i];
if (autoConfigurationClass != null) {
String candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");
if (candidates != null) {
outcomes[i - start] = getOutcome(candidates);
}
}
}
return outcomes;
}
// 判断配置类ConditionalOnClass上的类,是否存在,不存在就排除该配置类,不进行加载
private ConditionOutcome getOutcome(String candidates) {
try {
if (!candidates.contains(",")) {
return getOutcome(candidates, this.beanClassLoader);
}
for (String candidate : StringUtils.commaDelimitedListToStringArray(candidates)) {
ConditionOutcome outcome = getOutcome(candidate, this.beanClassLoader);
if (outcome != null) {
return outcome;
}
}
}
catch (Exception ex) {
// We'll get another chance later
}
return null;
}
private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
if (ClassNameFilter.MISSING.matches(className, classLoader)) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
.didNotFind("required class").items(Style.QUOTE, className));
}
return null;
}
通过下图可以看到,过滤完成后,在reader.loadBeanDefinitions(configClasses)时,会根据bean组件上的条件注解,在过滤一遍,这里自动配置和业务bean都会在check一下。
ComponentScan注册包下所有的bean:ConfigurationClassParser的doProcessConfigurationClass()中,根据 class信息的包名来扫描bean组件,由此也验证了猜想一猜对了:
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}