【SpringBoot1】Spring Boot是如何推断你的工程类型的

我们的工程一般都是基于java的Servlet的,但是除此之外还有其他类型的,

在WebApplicationType里定义了常见的类型:

复制代码
public enum WebApplicationType {
	/**
	 * The application should not run as a web application and should not start an
	 * embedded web server.
	 */
	NONE,
	/**
	 * The application should run as a servlet-based web application and should start an
	 * embedded servlet web server.
	 */
	SERVLET,

	/**
	 * The application should run as a reactive web application and should start an
	 * embedded reactive web server.
	 */
	REACTIVE;

从定义上也能看到,第一种是None类型,表示不是网络服务的类型,如果我们的服务不是web服务可以这么设置,但是这个应用场景极少。

第二种就是我们常用的SERVLRT方式,一般我们工程都要采用这种方式,即使不需要对外部用户提供服务,也需要与监控系统等交互,因此一般我们设置为Servet就行了。

第三种是REACTIVE,这个应该是支持响应式编程的,这个目前接触不多,暂且略过。

那么SpringBoot是如何判断我们的服务是哪种类型的呢?

一般我们SpringBoot的main入口都是这么写的:

复制代码
@SpringBootApplication
@MapperScan("com.online_education.storage.mapper;")
public class TeacherServiceApp {
    public static void main(String[] args) {
        SpringApplication.run(TeacherServiceApp.class,args);

    }
}

其中@SpringBootApplication注解里其实是集成了三个:

复制代码
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(...)

这三个注解的功能我们后面分析,这里我们先回到main方法里的run()继续看:

复制代码
SpringApplication.run(TeacherServiceApp.class,args);

此时内容是这样的:

复制代码
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}

这里的run没做什么,我们继续看run里的代码:

复制代码
return new SpringApplication(primarySources).run(args);

这里主要是用反射创建我们的Main方法所在的对象,我们首先进入SpringApplication类里看一下,

复制代码
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		this.mainApplicationClass = deduceMainApplicationClass();
	}

这里有一行很重要的代码:

复制代码
this.webApplicationType = WebApplicationType.deduceFromClasspath();

这一行就是获取类型的,那具体如何获取的呢?我们继续看:

复制代码
	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;
	}

这里的isPresent()做什么的我们后面再细看,这里通过名字也能推测出来,大致是要判断是否存在的意思,第一个if部分就是判断是否为响应式REACTIVE类型,后面的for循环就是判断是否存在

SERVLET_INDICATOR_CLASSES里标记的类型,里面两个参数是web服务必须的,只要有一个不存在就会返回类型是空。

复制代码
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
			"org.springframework.web.context.ConfigurableWebApplicationContext" };

可以看到,这里其实是两个写死的类的地址,因此这里其实主要到某个地方查是否加载了这两个类。

如果都存在,那就返回Servlet类型。

那具体到哪里查的呢?我们继续看ClassUtils.isPresent(className, null)的实现:

复制代码
  public static boolean isPresent(String className, @Nullable ClassLoader classLoader) {
        try {
            forName(className, classLoader);
            return true;
        } catch (IllegalAccessError var3) {
            throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" + className + "]: " + var3.getMessage(), var3);
        } catch (Throwable var4) {
            return false;
        }
    }

到这里,根据定义和forName这种写法,有经验的同学就知道了,这里是到JVM里查装载器是否载入了。

复制代码
public static Class<?> forName(String name, @Nullable ClassLoader classLoader){
... 
Class elementClass;
            String elementName;
            if (name.endsWith("[]")) {
                elementName = name.substring(0, name.length() - "[]".length());
                elementClass = forName(elementName, classLoader);
                return Array.newInstance(elementClass, 0).getClass();
            } else if (name.startsWith("[L") && name.endsWith(";")) {
                elementName = name.substring("[L".length(), name.length() - 1);
                elementClass = forName(elementName, classLoader);
                return Array.newInstance(elementClass, 0).getClass();
            } else if (name.startsWith("[")) {
                elementName = name.substring("[".length());
                elementClass = forName(elementName, classLoader);
                return Array.newInstance(elementClass, 0).getClass();
            } else {
                ClassLoader clToUse = classLoader;
                if (classLoader == null) {
                    clToUse = getDefaultClassLoader();
                }

                try {
                    return Class.forName(name, false, clToUse);
... 
}

这里的写法就比较负责了,上面是一个递归,本质上是在读字节码文件,例如name.startsWith("[L") 这些都是读字节码用的。

那字节码是保存在哪里的呢?学过JVM虚拟机原理的同学应该知道,字节码是保存在元空间的,所以这里SpringBoot在启动的时候是去元空间里查,看看我们有没有将Servlet的类信息加载进来。

到此我们就知道了,如果我们要启动web服务,需要先将Servlet加载进来,SpringBoot还会以此类型来判断是否启用web服务。

而且通过这个代码,我们也可以知道Servlet和响应式只能装载一个,如果两个都有,仍然会当做Servelt服务。

复制代码
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
			return WebApplicationType.REACTIVE;
		}

很明显,如果我们启动SpringBoot的时候还没有将Servlet相关类加载进来,那自然启动就失败了。

相关推荐
极小狐21 分钟前
极狐GitLab 18.5 正式发布,更新 Maven 虚拟仓库 UI(Beta)、全新个人主页、实例级合规与安全策略管理 以及 DAST 认证脚本 等
java·gitlab·maven
王元_SmallA37 分钟前
【玩转全栈】----Django基本配置和介绍
java·后端
Moonbit1 小时前
招募进行时 | MoonBit AI : 程序语言 & 大模型
前端·后端·面试
LiuYaoheng1 小时前
【Android】Drawable 基础
android·java
AlianNiew1 小时前
从源码到实战:用 Java 打造“限时+防重放”的文件安全预览链接
java·后端
星梦清河1 小时前
Redis(四):缓存击穿及其解决方案(SpringBoot+mybatis-plus)
spring boot·redis·缓存
null or notnull2 小时前
java服务器空间不够时:将多个服务器的文件存放至同一个服务器上(使用映射器的办法)
java·运维·服务器·java-ee
FAFU_kyp2 小时前
WebMvcConfig 和 WebSecurityConfig 详解
spring boot·java-ee
小威要向诸佬学习呀2 小时前
2025年软件外包避坑指南与平台推荐:开发者实用经验分享
后端
冒泡的肥皂2 小时前
2PL+MVCC看一些场景
数据库·后端·mysql