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


图注:爱折腾的稻草图注:爱折腾的稻草


猜你喜欢

转载自blog.51cto.com/13836814/2134374
今日推荐