普通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应用可以作为独立的服务器进程长期运行。

相关推荐
带刺的坐椅1 天前
Solon AI Skills 会是 Agent 的未来吗?
java·agent·langchain4j·solon-ai
jacGJ1 天前
记录学习--文件读写
java·前端·学习
花间相见1 天前
【JAVA开发】—— Nginx服务器
java·开发语言·nginx
扶苏-su1 天前
Java---Properties 类
java·开发语言
cypking1 天前
四、CRUD操作指南
java
橙子家1 天前
WebAPI 项目通过 CI/CD 自动化部署到 Linux 服务器(docker-compose)
后端
2301_780669861 天前
文件字节流输出、文件复制、关闭流的方法
java
剑锋所指,所向披靡!1 天前
C++之类模版
java·jvm·c++
钟离墨笺1 天前
Go语言--2go基础-->基本数据类型
开发语言·前端·后端·golang
Coder_Boy_1 天前
基于SpringAI的在线考试系统-0到1全流程研发:DDD、TDD与CICD协同实践
java·人工智能·spring boot·架构·ddd·tdd