SpringBoot 啟動解析
入口
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class JamsAuthApplication {
public static void main(String[] args) {
SpringApplication.run(JamsAuthApplication.class, args);
}
}
主要注解
@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是一個組合注解,主要包含@SpringBootConfiguration、@EnableAutoConfiguration,@ComponentScan等幾個注解,并沒有什么特別的地方。
@SpringBootConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
@SpringBootConfiguration實際上是@Configuration。
@Configuration
基于JavaConfig形式的Spring Ioc容器的配置類。
任何一個標注了@Configuration的Java類定義都是一個JavaConfig配置類;
任何一個標注了@Bean的方法,其返回值將作為一個bean定義注冊到Spring的IoC容器,方法名將默認成該bean定義的id。
@ComponentScan
@ComponentScan的功能其實就是自動掃描并加載符合條件的組件(比如@Component和@Repository等)或者bean定義,最終將這些bean定義加載到IoC容器中。
相當于配置中的:
<context:component-scan>
我們可以通過basePackages等屬性來細粒度的定制@ComponentScan自動掃描的范圍,如果不指定,則默認Spring框架實現會從聲明@ComponentScan所在類的package進行掃描,所以SpringBoot的啟動類最好是放在root package下,因為默認不指定basePackages。
@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 {};
}
這個注解是SpringBoot自動配置的關鍵,主要是借助@Import的幫助,將所有符合自動配置條件的bean定義加載到IoC容器,后面有機會在深入。
入口方法
SpringApplication.run(JamsAuthApplication.class, args);
進入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);}
上面的代碼會創建一個SpringApplication實例,并調用其run方法,先看構造方法。
1.SpringApplication構造方法
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
//調用重載構造方法
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 判斷是否是web程序(javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext都必須在類加載器中存在),并設置到webEnvironment屬性中
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 從spring.factories文件中找出key為ApplicationContextInitializer的類并實例化后設置到SpringApplication的initializers屬性中。這個過程也就是找出所有的應用程序初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 從spring.factories文件中找出key為ApplicationListener的類并實例化后設置到SpringApplication的listeners屬性中。這個過程就是找出所有的應用程序事件監聽器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 找出main類
this.mainApplicationClass = deduceMainApplicationClass();
}
deduceFromClasspath():推斷類型
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
setInitializers():設置程序初始化器
從spring.factories文件中找出key為ApplicationContextInitializer的類并實例化后設置到SpringApplication的initializers屬性中。這個過程也就是找出所有的應用程序初始化器.
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// 使用Set保存names來避免重復元素,加載在 META-INF/spring.factories 里的類名的數組
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 根據names來進行實例化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
//// 對實例進行排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
設置初始值
public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<>();
this.initializers.addAll(initializers);
}
setListeners():設置監聽器
從spring.factories文件中找出key為ApplicationListener的類并實例化后設置到SpringApplication的listeners屬性中。這個過程就是找出所有的應用程序事件監聽器,
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
與初始化initializers相似 。
deduceMainApplicationClass():推斷應用入口類
this.mainApplicationClass = deduceMainApplicationClass();
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
它通過構造一個運行時異常,通過異常棧中方法名為main的棧幀來得到入口類的名字。
2.SpringApplication.run()方法
public ConfigurableApplicationContext run(String... args) {
// 計時工具,StopWatch 主要用于簡單統計 run 啟動過程的時長
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 設置java.awt.headless系統屬性為true - 沒有圖形化界面
configureHeadlessProperty();
// 1.獲得SpringApplicationRunListener 的數組,并啟動監聽
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 2.加載屬性配置。執行完成后,所有的 environment 的屬性都會加載進來,包括 application.properties 和外部的屬性配置
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
//準備Banner打印
Banner printedBanner = printBanner(environment);
//3.創建Spring容器
context = createApplicationContext();
// 準備異常報告器
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//4.Spring容器前置處理
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新Spring 容器
refreshContext(context);
//Spring容器后置處理
afterRefresh(context, applicationArguments);
//停止計時器
stopWatch.stop();
if (this.logStartupInfo) {
//打印 Spring Boot 啟動的時長日志
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 通知 SpringApplicationRunListener 的數組,Spring 容器啟動完成
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
//處理異常
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
//通知 SpringApplicationRunListener 的數組,Spring 容器正在運行
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
1.getRunListeners():所謂獲得數組,并啟動監聽。
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
可以看到這里也是調用了getSpringFactoriesInstances()方法來獲取org.springframework.boot.SpringApplicationRunListener的values.作用后面遇到再細看。
2.prepareEnvironment():根據SpringApplicationRunListeners以及參數來準備環境
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// 根據 webApplicationType 類型,會創建不同類型的 ConfigurableEnvironment 對象
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置環境
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
// 通知監聽器環境變量準備完成
listeners.environmentPrepared(environment);
// 綁定
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
configureEnvironment():配置環境:Property Sources和Profiles
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
}
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
3.createApplicationContext():創建spring上下文
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
4.prepareContext():容器前置處理
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
// 關聯環境與容器
context.setEnvironment(environment);
// 為容器配置Bean生成器以及資源加載器
postProcessApplicationContext(context);
// 初始化
applyInitializers(context);
// 通知監聽器容器準備完成
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
//添加兩個特殊的bean
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// 加載所有資源
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
// 通知監聽器容器加載完成。
listeners.contextLoaded(context);
}
可以看到這里applyInitializers(context)用到了實例化設置的初始化器了
protected void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
總結
以上大概走了springboot的啟動流程,中間還有一些方法尚不清楚,待后續補充...