62

SpringBoot源码解析之应用类型识别

 5 years ago
source link: http://www.choupangxia.com/topic/detail/145?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

创建SpringBoot项目时,如果不选择starter-web,创建的SpringBoot项目可以正常运行,但运行结束程序便终止了。如果配置starter-web,则正常启动web应用。那么,SpringBoot是如何分辨出来当前应用是为web应用还是其他类型的应用呢?本篇文章带领大家从源码层面进行相应分析。

枚举WebApplicationType

SpringBoot使用枚举类WebApplicationType来定义可支持的应用类型以及相关推断应用类型的常量(数组)及静态方法。下面对该枚举类进行详细的讲解。

应用类型

枚举WebApplicationType中定义了三个应用类型:

  • NONE:应用程序不作为web应用启动,不启动内嵌的服务。
  • SERVLET:应用程序以基于servlet的web应用启动,需启动内嵌servlet web服务。
  • REACTIVE:应用程序以响应式web应用启动,需启动内嵌的响应式web服务。

推断应用类型

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()方法主要做的事情就是获得类加载器,尝试直接加载类,如果失败则尝试加载该类的内部类,如果依旧失败,则抛出异常。

因此,整个应用类型的推断分以下步骤:

  • SpringBoot调用SpringApplication构造方法;
  • SpringApplication构造方法调用枚举类的类型推断方法deduceFromClasspath()。
  • deduceFromClasspath()方法通过ClassUtils.isPresent()返回结果为true或false来确定是否加载成功指定的类。
  • ClassUtils.isPresent()方法通过调用forName()方法并捕获异常来确定是否能够成功加载该类。
  • 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";
  • 如果应用程序存在DispatcherHandler并且不存在DispatcherServlet和ServletContainer则为响应式web应用,需加载并启动内嵌的响应式web服务。
  • 如果应用程序不包含Servlet和ConfigurableWebApplicationContext则为普通应用程序。
  • 其他情况则为基于servlet的web应用,需加载并启动内嵌的web web服务。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK