SpringBoot原始碼解析之應用型別識別
建立SpringBoot專案時,如果不選擇starter-web,建立的SpringBoot專案可以正常執行,但執行結束程式便終止了。如果配置starter-web,則正常啟動web應用。那麼,SpringBoot是如何分辨出來當前應用是為web應用還是其他型別的應用呢?本篇文章帶領大家從原始碼層面進行相應分析。
列舉WebApplicationType
SpringBoot使用列舉類WebApplicationType來定義可支援的應用型別以及相關推斷應用型別的常量(陣列)及靜態方法。下面對該列舉類進行詳細的講解。
應用型別
列舉WebApplicationType中定義了三個應用型別:
- NONE:應用程式不作為web應用啟動,不啟動內嵌的服務。
- SERVLET:應用程式以基於servlet的web應用啟動,需啟動內嵌servlet web服務。
- REACTIVE:應用程式以響應式web應用啟動,需啟動內嵌的響應式web服務。
推斷應用型別
SpringBoot啟動時,在建立SpringApplication的構造方法內會呼叫列舉WebApplicationType的deduceFromClasspath方法獲得應用型別並設定當前應用是普通web應用、響應式web應用還是非web應用。
SpringApplication的構造方法中呼叫並設定原始碼:
this.webApplicationType = WebApplicationType.deduceFromClasspath();
deduceFromClasspath方法由列舉WebApplicationType提供,具體實現如下:
static WebApplicationType deduceFromClasspath() { if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) { return WebApplicationType.REACTIVE; } for (String className : SERVLET_INDICATOR_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return WebApplicationType.NONE; } } return WebApplicationType.SERVLET; }
推斷的過程中重點呼叫了ClassUtils.isPresent()方法,用來判斷指定類名的類是否存在,是否可以進行載入。ClassUtils.isPresent()方法原始碼如下:
public static boolean isPresent(String className, @Nullable ClassLoader classLoader) { try { forName(className, classLoader); return true; }catch (IllegalAccessError err) { throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" + className + "]: " + err.getMessage(), err); }catch (Throwable ex) { return false; } }
isPresent()方法呼叫了forName()方法,如果在呼叫forName()方法的過程中出現異常則返回false,也就是目標類不存在。否則,返回true。
看一下forName()方法的部分程式碼:
public static Class<?> forName(String name, @Nullable ClassLoader classLoader) throws ClassNotFoundException, LinkageError { // 此處省略一些非空和基礎型別的判斷邏輯程式碼 ClassLoader clToUse = classLoader; if (clToUse == null) { //如果為空則獲取預設classLoader clToUse = getDefaultClassLoader(); } try { // 返回載入戶的Class。 return Class.forName(name, false, clToUse); } 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 Class.forName(innerClassName, false, clToUse); } catch (ClassNotFoundException ex2) { // Swallow - let original exception get through } } throw ex; } }
通過以上核心程式碼,可得知forName()方法主要做的事情就是獲得類載入器,嘗試直接載入類,如果失敗則嘗試載入該類的內部類,如果依舊失敗,則丟擲異常。
因此,整個應用型別的推斷分以下步驟:
- SpringBoot呼叫SpringApplication構造方法;
- SpringApplication構造方法呼叫列舉類的型別推斷方法deduceFromClasspath()。
- deduceFromClasspath()方法通過ClassUtils.isPresent()返回結果為true或false來確定是否載入成功指定的類。
- ClassUtils.isPresent()方法通過呼叫forName()方法並捕獲異常來確定是否能夠成功載入該類。
- forName()方法通過嘗試載入指定類和指定類的內部類來確定該類是否存在,存在則返回該類,不存在則拋異常。
在型別推斷的過程中列舉類WebApplicationType定義了具體去載入哪些類:
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" }; private static final String WEBMVC_INDICATOR_CLASS = "org.springframework." + "web.servlet.DispatcherServlet"; private static final String WEBFLUX_INDICATOR_CLASS = "org." + "springframework.web.reactive.DispatcherHandler"; private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
- 如果應用程式存在DispatcherHandler並且不存在DispatcherServlet和ServletContainer則為響應式web應用,需載入並啟動內嵌的響應式web服務。
- 如果應用程式不包含Servlet和ConfigurableWebApplicationContext則為普通應用程式。
- 其他情況則為基於servlet的web應用,需載入並啟動內嵌的web web服務。