[SpringBoot]深入淺出剖析SpringBoot的應用類型識別機制

微信號:GitShare
微信公衆號:愛折騰的稻草
如有問題或建議,請在公衆號留言[1]

前續

爲幫助廣大SpringBoot用戶達到“知其然,更需知其所以然”的境界,作者將通過SpringBoot系列文章全方位對SpringBoot2.0.0.RELEASE版本深入分解剖析,讓您深刻的理解其內部工作原理。

推斷應用的類型

SpringBoot啓動時,在創建SpringApplication對象時,會自動去識別並設置當前應用是普通web應用、響應式web應用還是非web應用,其內部實現原理是什麼?  
首先看看源代碼

/**
* 推斷應用的類型
*/

private WebApplicationType deduceWebApplicationType() {
    if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
            && !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
        return WebApplicationType.REACTIVE;
    }
    for (String className : WEB_ENVIRONMENT_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return WebApplicationType.NONE;
        }
    }
    return WebApplicationType.SERVLET;
}
  • ClassUtils.isPresent()方法:  
    其作用是判斷所提供的類名的類是否存在,且可以被加載。源代碼如下:

public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
    try {
        forName(className, classLoader);
        return true;
    }
    catch (Throwable ex) {
        // Class or one of its dependencies is not present...
        return false;
    }
}

調用了forName()方法,如果出現異常,則返回false,也就是提供目標類不存在。

  • forName()方法的源碼剖析:

public static Class<?> forName(String name, @Nullable ClassLoader classLoader)
        throws ClassNotFoundException, LinkageError {

    Assert.notNull(name, "Name must not be null");
    //根據基本類的JVM命名規則(如果合適的話),將給定的類名name解析爲基本類型的包裝類
    Class<?> clazz = resolvePrimitiveClassName(name);
    if (clazz == null) {
        //commonClassCache是包含java.lang包下所有類,將類的類名作爲鍵,對應類作爲值的一個Map集合。
        clazz = commonClassCache.get(name); //根據類名,獲取commonClassCache集合中的值,如果爲空,表示目標類不是java.lang包的下類,即不是原始類型。
    }
    if (clazz != null) {
        //如果根據類名,已經獲取到了類,則直接返回該類。
        return clazz;
    }

    //判斷clas屬性值是否爲數組對象。比如:java.lang.String[]
    // "java.lang.String[]" style arrays
    if (name.endsWith(ARRAY_SUFFIX)) { 
        //如果是,則將類名後的“[]”方括號截去,返回java.lang.String,遞歸查找類名,找到後,將Class類型轉換爲數組。
        String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length());
        Class<?> elementClass = forName(elementClassName, classLoader);
        return Array.newInstance(elementClass, 0).getClass();
    }

    //class屬性值是否爲數組對象的二進制表示。比如:[Ljava.lang.String
    // "[Ljava.lang.String;" style arrays
    if (name.startsWith(NON_PRIMITIVE_ARRAY_PREFIX) && name.endsWith(";")) {
        //如果是,則將值的“[L”部分截去,遞歸查找類名,找到後,將對應的Class類型轉換爲數組
        String elementName = name.substring(NON_PRIMITIVE_ARRAY_PREFIX.length(), name.length() - 1);
        Class<?> elementClass = forName(elementName, classLoader);
        return Array.newInstance(elementClass, 0).getClass();
    }

    //class屬性值是否爲二維數組
    // "[[I" or "[[Ljava.lang.String;" style arrays
    if (name.startsWith(INTERNAL_ARRAY_PREFIX)) {
        String elementName = name.substring(INTERNAL_ARRAY_PREFIX.length());
        Class<?> elementClass = forName(elementName, classLoader);
        return Array.newInstance(elementClass, 0).getClass();
    }

    //獲取classLoader
    ClassLoader clToUse = classLoader;
    if (clToUse == null) {
         //如果classLoader爲空,則獲取默認的classLoader對象。
        clToUse = getDefaultClassLoader();
    }
    try {
        //返回加載後的類
        return (clToUse != null ? clToUse.loadClass(name) : Class.forName(name));
    }
    catch (ClassNotFoundException ex) {
        //用於處理內部類的情況。
        int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR);
        if (lastDotIndex != -1) {
            //拼接內部類的名字。
            String innerClassName = name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1);
            try {
                return (clToUse != null ? clToUse.loadClass(innerClassName) : Class.forName(innerClassName));
            }
            catch (ClassNotFoundException ex2) {
                // Swallow - let original exception get through
            }
        }
        throw ex;
    }
}
  • 再來看看使用到的常量值:

    • REACTIVE_WEB_ENVIRONMENT_CLASS:org.springframework.web.reactive.DispatcherHandler

    • MVC_WEB_ENVIRONMENT_CLASS:org.springframework.web.servlet.DispatcherServlet

    • WEB_ENVIRONMENT_CLASSES:{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" }
      也就是說,

  • 1、如果應用程序中存在org.springframework.web.reactive.DispatcherHandler這個類,則表示是一個響應式web應用,項目在啓動時,需要去
    加載啓動內嵌的響應式web服務器。

  • 2、如果應用程序中既不存在javax.servlet.Servlet類,也不存在org.springframework.web.context.ConfigurableWebApplicationContext這個類,則
    表示當前應用不是一個web應用,啓動時無需加載啓動內嵌的web服務器。

  • 3、除上述兩種情況外,則表示當前應用是一個servlet的web應用,啓動時需要加載啓動內嵌的servlet的web服務器(比如Tomcat)。

推斷並設置main方法的定義類(啓動類)

SpringBoot啓動時,在創建SpringApplication對象時,最後會推斷並設置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;
}
  • java.lang.StackTraceElement是什麼?
    類元素代表一個堆棧幀。除了一個在堆棧的頂部所有的棧幀代表一個方法調用。在堆棧頂部的幀表示在將其生成的堆棧跟蹤的執行點。

    • stackTraceElement.getMethodName() 返回一個包含由該堆棧跟蹤元素所表示的執行點的方法的名稱。

    • stackTraceElement.getClassName() 返回一個包含由該堆棧跟蹤元素所表示的執行點類的完全限定名。

  • java.lang.Class.forName()的作用是什麼?
    java.lang.Class.forName(String name, boolean initialize, ClassLoader loader) 方法返回與給定字符串名的類或接口的Class對象,使用給定的類加載器。

參數說明
    - name :這是所需類的完全限定名稱。
    - initialize : 這說明這個類是否必須初始化。
    - loader : 這是必須加載的類的類加載器。
異常說明
    - LinkageError : 如果聯動失敗。
    - ExceptionInInitializerError : 如果這種方法所引發的初始化失敗。
    - ClassNotFoundException : 如果類不能位於由指定的類加載器。
參數使用
    - ClassLoader loader:如果該參數加載器loader 爲空,通過引導類加載器加載類。
    - boolean initialize:如果它沒有被初始化,則initialize參數爲true

通過上面知識點的講解,deduceMainApplicationClass的作用就非常清晰了,主要是獲取當前方法調用棧,遍歷調用堆棧信息找到main函數的類,並返回該類。

總結

  • 1、SpringBoot是通過調用ClassUtils類的isPresent方法,檢查classpath中是否存在org.springframework.web.reactive.DispatcherHandler類、
    javax.servlet.Servlet類和org.springframework.web.context.ConfigurableWebApplicationContext類來判斷當前應用是響應式Web應用,還是普通的Servlet的Web應用,還是非Web應用。

  • 2、SpringBoot是通過獲取當前方法的調用棧信息,來判斷當前main函數所在的類。

後記

爲幫助廣大SpringBoot用戶達到“知其然,更需知其所以然”的境界,作者將通過SpringBoot系列文章全方位對SpringBoot2.0.0.RELEASE版本深入分解剖析,讓您深刻的理解其內部工作原理。 

敬請關注[愛折騰的稻草(GitShare)]公衆號,爲您提供更多更優質的技術教程。


圖注:愛折騰的稻草圖注:愛折騰的稻草


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