创建SpringBoot项目时,如果不选择starter-web,创建的SpringBoot项目可以正常运行,但运行结束程序便终止了。如果配置starter-web,则正常启动web应用。那么,SpringBoot是如何分辨出来当前应用是为web应用还是其他类型的应用呢?本篇文章带领大家从源码层面进行相应分析。
SpringBoot使用枚举类WebApplicationType来定义可支持的应用类型以及相关推断应用类型的常量(数组)及静态方法。下面对该枚举类进行详细的讲解。
枚举WebApplicationType中定义了三个应用类型:
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()方法主要做的事情就是获得类加载器,尝试直接加载类,如果失败则尝试加载该类的内部类,如果依旧失败,则抛出异常。
因此,整个应用类型的推断分以下步骤:
在类型推断的过程中枚举类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";
原文链接:https://www.choupangxia.com/topic/detail/145
CSDN学院:《Spring Boot 视频教程全家桶》