Springboot源碼分析 SpringApplication初始化
描述:衆所周知,springboot以輕量級/簡化著稱,其可通過Main方法直接運行項目
SpringApplication初始化過程如下
@SpringBootApplication
public class SpringBootDemo81Application {
public static void main(String[] args) {//步驟1
SpringApplication.run(SpringBootDemo81Application.class, args);
}
}
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified source using default settings.
* @param primarySource the primary source to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {//步驟2
return run(new Class<?>[] { primarySource }, args);
}
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified sources using default settings and user supplied arguments.
* @param primarySources the primary sources to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {//步驟3
return new SpringApplication(primarySources).run(args);
}
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);//步驟4
}
//步驟5
@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));//@1
this.webApplicationType = WebApplicationType.deduceFromClasspath();//@2
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));//@3
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));//@4
this.mainApplicationClass = deduceMainApplicationClass();//@5
}
如上所需,通過debug源碼跟進發現總共執行5個步驟
步驟1 傳入SpringBootDemo81Application類,此類是核心應用類,用於構建,args未傳入
步驟2 主要是構建SpringApplication 對象,然後調用其run方法
步驟3.4。.5用於構建SpringApplication 對象,詳細跟分析步驟5如下
@1設置primarySources 等於傳入的Springboot類
@2接着設置當前運行的環境,通過一個枚舉判斷當前所處的環境狀態
/**
* The application should not run as a web application and should not start an
* embedded web server.
*/
NONE,
/**
* The application should run as a servlet-based web application and should start an
* embedded servlet web server.
*/
SERVLET,
/**
* The application should run as a reactive web application and should start an
* embedded reactive web server.
*/
REACTIVE;
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;
}
@3初始化加載一些參數和配置
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();//@3-1
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(//@3-2
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);//@3-3
AnnotationAwareOrderComparator.sort(instances);//@3-4
return instances;
}
//@3-1獲取當前線程的ClassLoader
//@3-2加載配置如下
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
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()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
這段代碼主要是通過classLoader加載所以spring相關jar裏面META-INF/spring.factories的配置,涉及到工程如下(spring-boot、spring-boot-autoconfigure、spring-boot-devtools)
然後把當前線程的ClassLoader對應資源集合存儲到cache緩存中,便於後續使用
接着分析上面//@3-3
@SuppressWarnings("unchecked")
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass
.getDeclaredConstructor(parameterTypes);
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException(
"Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
之前步驟已經把相關類加載,此處主要是轉換實體,加入到集合中
//@3-4對集合進行排序
//@4同3一樣進行賦值並加入集合
//@5 獲取到Main函數啓動所在類
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函數所在類,並設置到SpringApplication對象的mainApplicationClass屬性
總結:
SpringApplication初始化,主要進行了項目包結構配置加載,並獲取當前方法的調用棧,找到main函數所在類所運行
作者簡介:張程 技術研究
更多文章請關注微信公衆號:zachary分解獅 (frankly0423)