[SpringBoot]深入浅出剖析SpringBoot的应用类型识别机制
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[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)]公众号,为您提供更多更优质的技术教程。
图注:爱折腾的稻草
以上是关于[SpringBoot]深入浅出剖析SpringBoot的应用类型识别机制的主要内容,如果未能解决你的问题,请参考以下文章