您好,登錄后才能下訂單哦!
微信號:GitShare
微信公眾號:愛折騰的稻草
如有問題或建議,請在公眾號留言[1]
為幫助廣大SpringBoot用戶達到“知其然,更需知其所以然”的境界,作者將通過SpringBoot系列文章全方位對SpringBoot2.0.0.RELEASE版本深入分解剖析,讓您深刻的理解其內部工作原理。
1、[SpringBoot]利用SpringBoot快速構建并啟動項目
2、[SpringBoot]詳解SpringBoot應用的啟動過程
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)。
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)]公眾號,為您提供更多更優質的技術教程。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。