Springboot源碼解析:一、SpringApplication的實例化

Springboot源碼解析:SpringApplication的實例化

打個廣告

個人想寫《springboot源碼解析》這一系列很久了,但是一直角兒心底的知識積累不足,所以一直沒有動筆。
所以想找一些小夥伴一起寫這一系列,互相糾錯交流學習。

如果有小夥伴有興趣一起把這一系列的講解寫完的話,加下我微信:13670426148,我們一起完成,當交流學習。

後期還想寫一系列介紹rpc框架的,不過要再過一陣子了,先把springboot的寫完

前言

這系列的教程從 Springboot項目的入口開始,即 SpringApplication.run(Application.class, args) 開始進行講解。

啓動入口

先貼一下入口類的代碼:

@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()方法的時候會進行處理,可以先記住他們。

現在可以記住 @EnableAutoConfiguration@EnableScheduling@ComponentScan 等註解,記住這些註解,後面將介紹其運行過程。

SpringApplication 實例化過程

Application 這個類沒有繼承所有任何類,他真的就是一個 啓動類,就相當與寫算法題時候的那個main函數,而你的計算流程就寫在其他類或者方法裏面。

SpringApplication用於從java main方法引導和啓動Spring應用程序,默認情況下,將執行以下步驟來引導我們的應用程序:

  • 創建一個恰當的ApplicationContext實例(取決於類路徑)
  • 註冊CommandLinePropertySource,將命令行參數公開爲Spring屬性
  • 刷新應用程序上下文,加載所有單例bean
  • 觸發全部CommandLineRunner bean

大多數情況下,像SpringApplication.run(ShiroApplication.class, args);這樣啓動我們的應用,也可以在運行之前創建和自定義SpringApplication實例,具體可以參考註釋中示例。

SpringApplication可以從各種不同的源讀取bean。 通常建議使用單個@Configuration類來引導,但是我們也可以通過以下方式來設置資源:

  • 通過AnnotatedBeanDefinitionReader加載完全限定類名
  • 通過XmlBeanDefinitionReader加載XML資源位置,或者是通過GroovyBeanDefinitionReader加載groovy腳本位置
  • 通過ClassPathBeanDefinitionScanner掃描包名稱
  • 也就是說SpringApplication還是做了不少事的,具體實現後續會慢慢講來,今天的主角只是SpringApplication構造方法。
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項目,其中有一個不能被加載到,說明不是。

獲取所有的實例化器Initializer.class

//看完這個方法真覺得很棒,獲取工廠實例
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;
    }



獲取所有的監聽器Initializer.class

類似的上面的步驟,監聽器的獲得結果如下: 圖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() 的 “ 廣播-事件-監聽器” 的執行過程, 爭取先喫透再解析。

參考鏈接:
spring-boot-2.0.3不一樣系列之源碼篇

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章