[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)]公众号,为您提供更多更优质的技术教程。


图注:爱折腾的稻草

原文地址:http://blog.51cto.com/13836814/2134374

时间: 2024-10-12 19:06:14

[SpringBoot]深入浅出剖析SpringBoot的应用类型识别机制的相关文章

[SpringBoot]深入浅出剖析SpringBoot中Spring Factories机制

微信号:GitShare微信公众号:爱折腾的稻草如有问题或建议,请在公众号留言[1] 前续 为帮助广大SpringBoot用户达到"知其然,更需知其所以然"的境界,作者将通过SpringBoot系列文章全方位对SpringBoot2.0.0.RELEASE版本深入分解剖析,让您深刻的理解其内部工作原理. 1.[SpringBoot]利用SpringBoot快速构建并启动项目 2.[SpringBoot]详解SpringBoot应用的启动过程 3.[SpringBoot]深入浅出剖析Sp

C 语言Struct 实现运行类型识别 RTTI

通过RTTI,能够通过基类的指针或引用来检索其所指对象的实际类型.c++通过下面两个操作符提供RTTI. (1)typeid:返回指针或引用所指对象的实际类型. (2)dynamic_cast:将基类类型的指针或引用安全的转换为派生类型的指针或引用. 对于带虚函数的类,在运行时执行RTTI操作符,返回动态类型信息:对于其他类型,在编译时执行RTTI,返回静态类型信息. 当具有基类的指针或引用,但需要执行派生类操作时,需要动态的强制类型转换(dynamic_cast).这种机制的使用容易出错,最好

第66课 C++中的类型识别

1. 类型识别 (1)在面向对象中可能出现下面的情况 ①基类指针指向子类对象 ②基类引用成为子类对象的别名 ▲静态类型——变量(对象)自身的类型(定义变量类型时类型或参数类型) ▲动态类型——指针(引用)所指向的对象的实际类型 (2)基类指针转子类指针: ①示例:Derived* d = static_cast<Derived*>(pBase); //危险的转换方式 ②问题:不安全,是否能强制类型转换取决动态类型. 2. 利用多态获取动态类型 (1)解决方案 ①在基类中定义虚函数,并返回具体的

C++ Primer 学习笔记_102_特殊工具与技术 --运行时类型识别[续]

特殊工具与技术 --运行时类型识别[续] 三.RTTI的使用 当比较两个派生类对象的时候,我们希望比较可能特定于派生类的数据成员.如果形参是基类引用,就只能比较基类中出现的成员,我们不能访问在派生类中但不在基类中出现的成员. 因此我们可以使用RTTI,在试图比较不同类型的对象时返回假(false). 我们将定义单个相等操作符.每个类定义一个虚函数 equal,该函数首先将操作数强制转换为正确的类型.如果转换成功,就进行真正的比较:如果转换失败,equal 操作就返回 false. 1.类层次 c

C++中的类型识别

1.C++中类型识别 (1)在面向对象中可能出现下面的情况 @1:基类指针指向子类对象 Base *p = new child(); @2:基类引用成为子类对象的别名 Base& r = *p; --上面的base是基类,child是这个基类的子类,第一种情况,由于赋值兼容性的存在,父类指针是可以指向子类对象的,但是我们无法通过父类指针来知道当前指针指向的是否是子类对象. --但是这时我们可以说,指针p的静态类型是Base*(指针期望的类型),指针p的动态类型是child(因为这时指针p指向的类

RTTI 运行时类型识别

RTTI   运行时类型识别 typeid  ------  dynamic_cast dynamic_cast 注意事项: 1.只能应用于指针和引用之间的转化 2.要转换的类型中必须包含虚函数 3.转换成功返回的是子类的地址,失败返回NULL typeid注意事项: 1.typeid返回一个type_info对象的引用 2.如果想通过基类获得派生类的数据类型,基类必须带有虚函数 3.只能获取对象的实际类型

类型识别

类型识别: 由于JS属于弱类型脚本语言,在变量赋值过程中,不考虑赋值对象类型,在程序编写过程中可能出现由于编写对象的类型不明确,导致各类诸如调用方法的使用不当等bug,因此需要进行类型识别.有以下几种方法: typeof:可以识别标准类型(Null除外),但不能识别具体的对象类型(function除外) Object.prototype.toString:可以识别标准类型以及内置对象类型,但不能识别自定义类型. function type(obj){ return Object.prototyp

C++杂记:运行时类型识别(RTTI)与动态类型转换原理

运行时类型识别(RTTI)的引入有三个作用: 配合typeid操作符的实现: 实现异常处理中catch的匹配过程: 实现动态类型转换dynamic_cast. 1. typeid操作符的实现 1.1. 静态类型的情形 C++中支持使用typeid关键字获取对象类型信息,它的返回值类型是const std::type_info&,例: #include <typeinfo> #include <cassert> struct B {} b, c; struct D : B {

Java基础之RTTI 运行时类型识别

运行时类型识别(RTTI, Run-Time Type Identification)是Java中非常有用的机制,在Java运行时,RTTI维护类的相关信息. 多态(polymorphism)是基于RTTI实现的.RTTI的功能主要是由Class类实现的. Class类 Class类是"类的类"(class of classes).如果说类是对象的抽象和集合的话,那么Class类就是对类的抽象和集合. 每一个Class类的对象代表一个其他的类.比如下面的程序中,Class类的对象c1代