Springboot源碼解析:SpringApplication的實例化java
我的想寫《springboot源碼解析》這一系列好久了,可是一直角兒心底的知識積累不足,因此一直沒有動筆。 因此想找一些小夥伴一塊兒寫這一系列,互相糾錯交流學習。web
若是有小夥伴有興趣一塊兒把這一系列的講解寫完的話,加下我微信:13670426148,咱們一塊兒完成,當交流學習。算法
後期還想寫一系列介紹rpc框架的,不過要再過一陣子了,先把springboot的寫完spring
這系列的教程從 Springboot項目的入口開始,即 SpringApplication.run(Application.class, args) 開始進行講解。springboot
先貼一下入口類的代碼:微信
@SpringBootApplication
//@EnableTransactionManagement
@EnableAsync
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
@EnableScheduling
@EnableRetry
@ComponentScan(basePackages = {"*** ", "***"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
複製代碼
其中,入口類的類名是 Application, 這個類的類型將做爲參數,傳遞給 SpringApplication的 run() 方法,還有一些初始化參數,這些都在run()方法的時候會進行處理,能夠先記住他們。mybatis
如今能夠記住 @EnableAutoConfiguration
和 @EnableScheduling
和 @ComponentScan
等註解,記住這些註解,後面將介紹其運行過程。框架
Application 這個類沒有繼承全部任何類,他真的就是一個 啓動類,就至關與寫算法題時候的那個main函數,而你的計算流程就寫在其餘類或者方法裏面。less
SpringApplication用於從java main方法引導和啓動Spring應用程序,默認狀況下,將執行如下步驟來引導咱們的應用程序:函數
大多數狀況下,像SpringApplication.run(ShiroApplication.class, args);這樣啓動咱們的應用,也能夠在運行以前建立和自定義SpringApplication實例,具體能夠參考註釋中示例。
SpringApplication能夠從各類不一樣的源讀取bean。 一般建議使用單個@Configuration類來引導,可是咱們也能夠經過如下方式來設置資源:
public class SpringApplication{
public SpringApplication(ResourceLoader resourceLoader, Object... sources) {
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.headless = true;
this.registerShutdownHook = true;
//todo -------------------------------------------------------
this.additionalProfiles = new HashSet();
//上面的信息都不是主要的,主要的信息在這裏,在這裏進行
//(1)運行環境 (2) 實例化器 (3)監聽器等的初始化過程,下面將詳細解析
this.initialize(sources);
}
public ConfigurableApplicationContext run(String... args) {
*******
}
}
複製代碼
這個 this.initialize(sources) 方法仍是在 SpringApplication裏面的,因此這個SpringApplication真的是貫穿springboot整個啓動過程的一個類,後面還有一個run() 方法。
咱們來看 initialize(Object[] sources) 方法的內容
private void initialize(Object[] sources) {
//sources裏面就是咱們的入口類: Application.class
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
//這行代碼設置SpringApplication的屬性webEnvironment,deduceWebEnvironment方法是推斷是不是web應用的核心方法
this.webEnvironment = this.deduceWebEnvironment();
//獲取全部的實例化器Initializer.class this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
//獲取全部的監聽器
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
//這個不解釋了,就是咱們的Application.class ,咱們寫的入口類,過程就是從當前的堆棧中找到咱們寫main方法額類,就是獲取咱們的入口類了
this.mainApplicationClass = this.deduceMainApplicationClass();
}
複製代碼
下面就解釋3個部分的具體實現:
(1) 推測運行環境
(2)獲取全部的實例化器Initializer.class
又展現了其獲取過程
(3)獲取全部的監聽器Initializer.class
推測運行環境,並賦予個 this.webEnvironment 這個屬性, deduceWebEnvironment方法是推斷是不是web應用的核心方法。 在後面SpringApplication 的run()方法中建立 ApplicationContext 的時候就是根據webEnvironment這個屬性來判斷是 AnnotationConfigEmbeddedWebApplicationContext
仍是 AnnotationConfigApplicationContext
代碼以下:
private boolean deduceWebEnvironment() {
String[] var1 = WEB_ENVIRONMENT_CLASSES;
int var2 = var1.length;
for(int var3 = 0; var3 < var2; ++var3) {
String className = var1[var3];
if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
return false;
}
}
return true;
}
WEB_ENVIRONMENT_CLASSES = new String[]{
"javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext"
};
複製代碼
推斷過程很簡單,不過我不理解爲何這麼寫,由於我這個是web項目,因此 this.webEnvironment
的值爲true
ClassUtils.isPresent()的過程其實很簡單,就是判斷 WEB_ENVIRONMENT_CLASSES
裏面的兩個類能不能被加載,若是能被加載到,則說明是web項目,其中有一個不能被加載到,說明不是。
//看完這個方法真以爲很棒,獲取工廠實例
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
return this.getSpringFactoriesInstances(type, new Class[0]);
}
//記住 ty
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
//這個是獲取類加載器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
//type是ApplicationContextInitializer.class,獲取類型工廠的名字
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//獲取工廠實例
List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
//排序,按照@Order的順序進行排序,沒有@Order的話,就按照本來的順序進行排序,無論他問題不大
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
複製代碼
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
//獲取全部 jar包下面的 META-INF/spring.factories 的urls
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
ArrayList result = new ArrayList();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
//每一個spring.factories裏的下的內容裝載成Properties信息
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
//下面內容會繼續解析
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
} catch (IOException var8) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
}
}
複製代碼
圖1.1以下:
還有不少,就不一一列舉出來了
(1)就是找到全部的 /MEIT-INF下面的spring.factory
(2)轉換成 properties,
(3)properties.getProperty(factoryClassName)
關於 /MEIT-INF/spring.factory,不知道你們有沒有寫過 starter,若是不知道是什麼,不少依賴好比mybatis-plus 、springboot的包裏面都有不少依賴,打成starter的形式,被咱們springboot項目依賴。
能夠查一查springboot自定義starter,看一下,大概就知道一個spring.factory的做用了。。
此時 factoryClassName 至關因而一個key獲取對應的properties裏面是否有 "org.springframework.context.ApplicationContextInitializer"對應的值,有的話,添加到result中
到最後能夠獲得的有,即圖1.2
再進行 獲取工廠實例 操做,步驟很簡單,就是用構造器和類名生成指定的 Inializer, 到如今的過程都很簡單。
貼個代碼,不進行解釋了
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;
}
複製代碼
相似的上面的步驟,監聽器的得到結果以下: 圖1.3
(1)還記得SpringApplication.class有那些屬性嗎
public class SpringApplication{
private List<ApplicationContextInitializer<?>> initializers; //如圖1.2這個是拿6個Initializer
private WebApplicationType webApplicationType; //這個是true
private List<ApplicationListener<?>> listeners; //這和是圖1.3的10個Listener
private Class<?> mainApplicationClass; //結果就是DemoApplication
//另外還有構造方法設置的值
public SpringApplication(ResourceLoader resourceLoader, Object... sources) {
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.headless = true;
this.registerShutdownHook = true;
//todo -------------------------------------------------------
this.additionalProfiles = new HashSet();
//上面的信息都不是主要的,主要的信息在這裏,在這裏進行
//(1)運行環境 (2) 實例化器 (3)監聽器等的初始化過程,下面將詳細解析
this.initialize(sources);
}
}
複製代碼
(2) SpringApplication.class 就是一個操做啓動過程的類
實例化過程就是加載一些最初始的參數和信息,好比監聽器,實例化器,bannerMode,additionalProfiles等信息。其中最主要的仍是監聽器和實例化器, 關於監聽器,是springboot啓動過程最重要的一部分,其啓動過程的機制大概就是, 用一個廣播,他廣播一些event事件,而後這些監聽器(10個),就會根據這些事件,作不一樣的反應。 監聽器模式你們能夠先了解一下。
下面會講 SpringApplication.run() 裏面的內容了,主要run() 的 「 廣播-事件-監聽器」 的執行過程, 爭取先吃透再解析。