SpringBoot原始碼---啟動流程分析
既然看到這篇文章了,那麼預設讀者已經很熟悉SpringBoot的使用的。
第一步,啟動一個SpringBoot應用:
@ComponentScan(basePackages = {""}) @MapperScan("") @SpringBootApplication public class StartApp { public static void main(String[] args) { SpringApplicationBuilder builder = new SpringApplicationBuilder(StartApp.class); // 修改Banner的模式為OFF builder.bannerMode(Banner.Mode.LOG).run(args); //載入系統配置項 } }
或者呢,可以這樣啟動:
@ComponentScan(basePackages = {""})
@MapperScan("")
@SpringBootApplication
public class StartApp {
public static void main(String[] args) {
SpringApplication sa = new SpringApplication(StartApp.class);
sa.run();
}
}
兩者是等價的,但是推薦使用第一種。為什麼呢?因為SpringApplicationBuilder對SpringApplication做了一些封裝,我們可以按需要設定更多定製化配置項。
第二步,SpringApplicationBuilder的例項化
public SpringApplicationBuilder(Class<?>... sources) {
this.application = createSpringApplication(sources);
}
protected SpringApplication createSpringApplication(Class<?>... sources) {
return new SpringApplication(sources);
}
以上就是在例項化一個SpringApplicationBuilder類時它為我們做的事情。可以發現,它間接例項化了一個SpringApplication物件,這就是它們為什麼等價的原因。好了,這些當然不是這篇文章的主要任務。我們接著往下看。
第三步,SpringApplication物件的例項化
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//resourceLoader的設定
this.resourceLoader = resourceLoader;
//斷言保證primarySources不能為空,也就是例項化SpringApplication時的引數不能為空
Assert.notNull(primarySources, "PrimarySources must not be null");
//把primarySources陣列轉換為set集合
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//判斷webApplicationType,也就是當前Springboot應用型別
this.webApplicationType = deduceWebApplicationType();
//收集ApplicationContextInitializer事件類
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//收集ApplicationListener事件類
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
在上述方法中,忽略一眼就能看明白的步驟,我們可以看到主要做了一些蒐集ApplicationContextInitializer和ApplicationListener類的工作。根據getSpringFactoriesInstances()方法的名稱,我們簡單判斷是一個從Spring容器中獲取指定型別物件的方法。那麼,具體看看它的實現:
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 = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(
//抽象出配置檔案中所有指定型別的Factory的名稱集合
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//建立Spring Factory類的例項集合
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
//排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
非常有意思的是SpringFactoriesLoader.loadFactoryNames(type, classLoader))方法這一步做的事情,它會把META-INF/spring.factories檔案中的所有Factory的名稱都掃描出來並放到集合中,程式碼如下:
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 {
//如果當前Springboot所線上程的classLoader 不為空,就從classLoader中獲取META-INF/spring.factories檔案;
//否則,從系統盤中獲取(針對不同執行環境)
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)) ;
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
//把
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
List<String> factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
}
}
到這裡,一些屬性設定相關的操作就完成了。當然,SpringApplicationBuilder和SpringApplication都支援很多個性化配置。我們可以自己去設定相關內容。
第四步,SpringApplicationBuilder的啟動run()方法。
public ConfigurableApplicationContext run(String... args) {
//如果已經啟動就直接返回當前context並停止後續操作
if (this.running.get()) {
// If already created we just return the existing context
return this.context;
}
//如果父容器不為空,那麼標記當前應用為子容器並啟動父容器(Maven多模組開發中會用到)
configureAsChildIfNecessary(args);
//如果當前應用沒有啟動就啟動它
if (this.running.compareAndSet(false, true)) {
synchronized (this.running) {
// If not already running copy the sources over and then run.
this.context = build().run(args);
}
}
return this.context;
}
可以看到,它呼叫了一個run()方法,由SpringApplication來實現的:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
//搜尋得到所有的啟動時ApplicationListener事件監聽類
SpringApplicationRunListeners listeners = getRunListeners(args);
//啟動所有的啟動時ApplicationListener事件監聽類
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
//列印Banner條
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
repareContext(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;
}
需要重點關注refreshContext(context)方法,它的實現如下:
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
好了,終於要放大招了。看到refresh(context)方法沒?它的實現在AbstractApplicationContext類中。這個類不用多說,你懂得^>^。