普通main方法 springboot项目main方法

Q:普通main方法执行完之后程序就停止了 但是启动springboot项目为什么就没有停止

A:

核心原因:Spring Boot创建了非守护线程

1. main方法执行后发生了什么

java 复制代码
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
        // 这里SpringApplication.run()会创建非守护线程
    }
}

2. SpringApplication.run()的工作原理

java 复制代码
// Spring Boot源码简化示意
public ConfigurableApplicationContext run(String... args) {
    // 1. 准备环境
    // 2. 创建应用上下文
    // 3. 刷新上下文(关键步骤)
    refreshContext(context);
    // 4. 执行Runner
    // 5. 返回上下文(但此时main线程已完成,程序继续运行)
}

3. 核心机制:嵌入式Web服务器创建非守护线程

Spring Boot启动时会根据依赖创建相应的Web服务器:

java 复制代码
// Tomcat为例
public class TomcatWebServer {
    public void start() throws WebServerException {
        // Tomcat会启动线程池,这些线程都是非守护线程
        this.tomcat.start();
        // 启动一个非守护线程来保持服务器运行
        startDaemonAwaitThread();
    }
    
    private void startDaemonAwaitThread() {
        Thread awaitThread = new Thread("container-" + (containerCounter.get())) {
            @Override
            public void run() {
                // 这个线程会一直阻塞,直到服务器关闭
                TomcatWebServer.this.tomcat.getServer().await();
            }
        };
        awaitThread.setDaemon(false); // 非守护线程!
        awaitThread.start();
    }
}

4. Java线程的守护/非守护机制

java 复制代码
public class ThreadExample {
    public static void main(String[] args) {
        // 守护线程:主线程结束后会自动结束
        Thread daemonThread = new Thread(() -> {
            while (true) {
                System.out.println("Daemon running...");
            }
        });
        daemonThread.setDaemon(true);
        daemonThread.start();
        
        // 程序会立即结束,因为只有守护线程
    }
}

5. Spring Boot保持运行的详细流程

text 复制代码
Spring Boot启动流程:
1. main线程调用 SpringApplication.run()
2. 创建Spring容器和应用上下文
3. 根据类路径检测到Web依赖(如spring-boot-starter-web)
4. 启动嵌入式Web服务器(Tomcat/Netty/Jetty)
5. Web服务器创建并启动非守护线程:
   - 监听端口(如8080)
   - 处理HTTP请求
   - 保持连接
6. main线程执行完毕,但JVM检测到还有非守护线程在运行
7. JVM保持运行,直到所有非守护线程结束

6. 验证:查看Spring Boot的线程

java 复制代码
// 可以在Spring Boot启动后查看线程
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
        
        // 查看所有线程
        Thread.getAllStackTraces().keySet().forEach(thread -> {
            System.out.println(thread.getName() + " - 是否守护线程: " + thread.isDaemon());
        });
    }
}

输出示例:

复制代码
main - 是否守护线程: false
Tomcat-startStop-1 - 是否守护线程: false
http-nio-8080-Acceptor - 是否守护线程: false
http-nio-8080-ClientPoller - 是否守护线程: false
...

7. 如何优雅关闭Spring Boot应用

java 复制代码
// 方式1:调用close()方法
context.close();

// 方式2:发送关闭信号(如Ctrl+C)
// Spring Boot会注册JVM关闭钩子
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    context.close();
}));

// 方式3:通过Actuator端点
// POST /actuator/shutdown

总结

Spring Boot项目在main方法执行完后不停止的原因是:

  1. Web服务器创建了非守护线程来监听端口和处理请求
  2. JVM规则:只要还有非守护线程在运行,JVM就不会退出
  3. 事件驱动模型:Web服务器在等待HTTP请求,处于阻塞状态
  4. 优雅的生命周期管理:需要显式关闭或接收关闭信号才会停止

这确保了Spring Boot应用可以作为独立的服务器进程长期运行。

相关推荐
曹牧2 小时前
文档格式:OFD
java
豆角焖肉3 小时前
Maven进阶与搭建私服
java·maven
大不点wow3 小时前
Java序列化与反序列化:让对象走出JVM
java·开发语言·jvm
止语Lab3 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活3 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
噢,我明白了3 小时前
Java中日期和字符串的处理
java·开发语言·日期
程序员天天困3 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn3 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
dkbnull3 小时前
Spring Boot请求处理组件对比详解
java·spring boot
jun_bai3 小时前
Orthanc服务器使用java上传dicom影像文件
java·运维·服务器