寫在前面
Springboot對Spring做了很好的封裝十气,僅通過添加依賴,以及一些有必要的配置外喧伞,就能夠完成項(xiàng)目的啟動(dòng)。下面我將通過對一個(gè)簡單的項(xiàng)目進(jìn)行調(diào)試追蹤,來發(fā)現(xiàn)Springboot是怎樣完成一個(gè)項(xiàng)目的啟動(dòng)的棚赔。
啟動(dòng)項(xiàng)目
SpringApplication.run()作為項(xiàng)目啟動(dòng)的唯一入口,接下來就到SpringApplication類中查看該方法執(zhí)行哪些事眶痰。
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);
}
可以看到本姥,通過將啟動(dòng)類的class對象作為參數(shù)傳進(jìn)來滓侍,通過SpringApplicatioin構(gòu)造方法新建一個(gè)對象裂逐,再調(diào)用其run方法。本篇先介紹SpringApplication對象的新建過程矮燎,下一篇再對run方法進(jìn)行介紹刊苍。
SpringApplication
public SpringApplication(Class<?>... primarySources) {
this((ResourceLoader)null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
主要看關(guān)鍵的幾步穗酥,首先是this.webApplicationType = WebApplicationType.deduceFromClasspath();
這一步是推斷應(yīng)用類型百揭。判斷出是servlet、reactive请毛、還是none方仿。這一步比較好理解委粉,就不從源碼上進(jìn)行分析了栗涂。
設(shè)置初始化器
第二步是設(shè)置初始化器墨吓,即setInitializers()
方法聪廉,該方法又通過getSpringFactoriesInstances()
方法進(jìn)行設(shè)置瞬痘。理解了getSpringFactoriesInstances()
方法,就能理解設(shè)置初始化器的過程板熊。以下是getSpringFactoriesInstances()
方法的源碼框全。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = this.getClassLoader();
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
在這一步里,通過類加載器去讀取factoryNames干签,再通過factoryNames構(gòu)建factory實(shí)例津辩。
1. 讀取factoryNames
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryImplementationName = var9[var11];
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
loadSpringFactories()
這個(gè)方法會到org.springframework.boot的jar包中讀取所有的META-INF/spring.factories里面的內(nèi)容。比如下面是spring-boot.jar包其中的Application Context Initializers內(nèi)容:
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
讀取這些文件后將內(nèi)容以鍵值對的形式存儲到Map<String, List<String>>中容劳。通過該方法讀取了# Application Context Initializers喘沿、# Application Listeners、# Environment Post Processors等等這樣一些寫在spring.factories文件中的factoryNames鸭蛙。再通過傳給loadFactoryNames()
方法的Class<?> factoryType
參數(shù)篩選出相對應(yīng)的factoryNames摹恨,即以Map的鍵名讀取值∪⑹樱回到setInitializers()
方法晒哄,傳入的參數(shù)為ApplicationContextInitializer.class
,因此獲取到的便是ApplicationContextInitializers
的factoryNames肪获。
2. 構(gòu)建factory實(shí)例
通過createSpringFactoriesInstances()
方法構(gòu)建factory實(shí)例寝凌,該方法的代碼如下:
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList(names.size());
Iterator var7 = names.iterator();
while(var7.hasNext()) {
String name = (String)var7.next();
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
T instance = BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
} catch (Throwable var12) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, var12);
}
}
return instances;
}
可以看到,這一步就是根據(jù)上一步得到的factory類名孝赫,通過反射機(jī)制創(chuàng)建factory實(shí)例较木。
設(shè)置監(jiān)聽器
setListeners()
方法與setInitializers()
方法類似,通過傳入ApplicationListener.class
對象青柄,從META-INF/spring.factories中讀取相應(yīng)的ApplicationListener
的factoryNames伐债。再利用反射機(jī)制對這些factory進(jìn)行實(shí)例化。需要指出的是致开,在loadSpringFactories()
方法中峰锁,由于setInitializers()
方法調(diào)用了一次并將結(jié)果存入cache中,因此setListeners()
方法再次調(diào)用時(shí)會將cache中保存的結(jié)果直接返回双戳。
推斷主應(yīng)用類
deduceMainApplicationClass()
方法通過跟蹤棧軌跡虹蒋,找出main方法所在的類,進(jìn)而推斷出我們項(xiàng)目的主應(yīng)用類。代碼如下:
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();
StackTraceElement[] var2 = stackTrace;
int var3 = stackTrace.length;
for(int var4 = 0; var4 < var3; ++var4) {
StackTraceElement stackTraceElement = var2[var4];
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
} catch (ClassNotFoundException var6) {
}
return null;
}
總結(jié)
回顧一下內(nèi)容魄衅,首先峭竣,啟動(dòng)項(xiàng)目分為兩步:創(chuàng)建SpringApplication對象、調(diào)用SpringApplication對象的run()方法晃虫。創(chuàng)建SpringApplication對象主要有四步:判斷應(yīng)用類型皆撩、設(shè)置初始化器、設(shè)置監(jiān)聽器哲银、判斷主應(yīng)用類毅访。其中設(shè)置初始化器和監(jiān)聽器都通過類加載器去讀取jar包中的META-INF/spring.factories文件,存儲到Map中盘榨,然后再通過反射機(jī)制完成factory的實(shí)例化喻粹。這就是SpringApplication對象創(chuàng)建過程完成的工作。下一篇我將對run()方法進(jìn)行介紹草巡。