微信号: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)]公众号,为您提供更多更优质的技术教程。
图注:爱折腾的稻草