走心Springboot源碼解析: 1、SpringApplication的實例化

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 等註解,記住這些註解,後面將介紹其運行過程。框架

SpringApplication 實例化過程

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

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不同系列之源碼篇

相關文章
相關標籤/搜索