spring boot 工作流程

Spring Boot 项目入口

Spring Boot 项目的入口是一个带有 main 方法的普通 Java 类,且该类必须被 @SpringBootApplication 注解标记。

入口文件的职责是 启动 Spring 容器、激活自动配置(自动装配 Bean)、组件扫描、启动内嵌服务器(tomcat/jetty/undertow)。具体的框架层面的工作流程是:

1>. 推断应用类型:判断是普通 Servlet 还是响应式(WebFlux)环境;

2>. 加载初始化器与监听器:从 META-INF/spring.factories 中读取配置;

3>. 创建并配置环境 (Environment):处理配置文件(application.yml 等);

4>. 创建容器 (Context):根据类型创建特定的 ApplicationContext;

5>. 刷新容器 (Refresh):这是最核心的一步,Spring 会在这里完成 Bean 的实例化、依赖注入以及自动配置核心逻辑;

注:ApplicationContext 指的是 Spring IoC 容器,它不是 Tomcat。IOC的职责是依赖注入 (DI)、控制反转 (IoC)、AOP等管理Java Bean对象;


项目入口文件仅在项目启动时运行一次,启动时进程层面的工作流程如下:

1>. JVM 启动

2>. Spring 容器初始化:扫描所有的 @Component, @Service, @RestController,并把它们实例化(创建对象)放入内存)

3>. 内嵌服务器启动:Tomcat 在某个端口(如 8080)开始监听。

4>. 初始化完成:此时 main 方法中的 SpringApplication.run() 执行完毕(或处于阻塞监听状态),整个应用准备就绪。

注:在运行阶段每次接收请求时,都不再经过项目入口 main 方法;请求直接由已经启动好的 内嵌服务器 (Tomcat) 接收,


Spring Boot 处理请求的流程

1>. 请求到达嵌入式服务器:首先由 Tomcat 等容器接收。

2>. 经过过滤器链 (Filter Chain):这是最外层防线。Spring Security 的认证、字符编码过滤等都在这里完成;

3>. 核心调度:DispatcherServlet:所有的请求都会汇聚到这个“前端控制器”,它负责分发任务;

4>. 查找处理器 (HandlerMapping):根据 @RequestMapping 的路径找到对应的 Controller 及其方法;

5>. 拦截器前置处理 (Interceptor - preHandle): 在进入 Controller 之前,执行自定义的拦截逻辑(如权限检查、日志记录)。

6>. 适配与执行 (HandlerAdapter):因为 Controller 的形式多样,Spring 使用适配器来统一调用具体的方法。此时会进行参数绑定(将 JSON 或 Query 参数转为 Java 对象)。

7>. 业务逻辑处理 (Controller/Service):进入业务代码;

8>. 结果处理与返回:如果是 RESTful 接口(@ResponseBody),则通过 HttpMessageConverter 将对象转换为 JSON 字符串写入响应体。

9>. 拦截器后置处理 (Interceptor - postHandle/afterCompletion):

10>. 请求完成后的清理工作。


线程和单例

Spring Boot 项目启动后就只有一个进程,其内部是通过多线程来处理并发请求的。

Spring Boot 默认是单例运行,这意味着同一个实例对象会处理所有请求。spring 的单例是指框架层面的核心业务组件是单例,如: @Controller、@Service、@Repository、@Component 等;但业务中的 DTO 对象、VO对象等都多例(因为每次都 new 实例化对象)。

单例意味都 Spring 只会为类创建一个对象实例;这样做的好处如下:

- 性能极高:不需要频繁地创建和销毁对象,减少了垃圾回收(GC)的压力。

- 节省内存:几千个请求共用一个实例,内存占用极低。


Spring boot 多线程:Spring Boot 本身只是一个框架,多线程实际由运行它的 内嵌服务器(如 Tomcat)来实现的。

当启动 Spring Boot 时,Tomcat 会初始化一个线程池(默认最大线程数通常是 200);

- 用户 A 发起请求:Tomcat 从线程池里抓出一个“线程 1”去处理 A 的请求;

- 用户 B 发起请求:Tomcat 再抓出一个“线程 2”去处理 B 的请求;

- 这两个线程会同时进入同一个的 Controller 实例的方法;


线程安全

由于 Bean 是单例(只有一个对象),而请求是多线程的,这意味着单例对象中的全局变量可能会被各个线程同时访问或修改,这就产生了一个非常重要的问题:线程安全。

因此,要保持 Bean 是“无状态”的 (Stateless)。即,永远不要在 Service 或 Controller 里定义“成员变量”来存储用户信息。所有的变量都应该定义在方法内部(作为局部变量)。

如果确实需要在当前线程中跨方法传递数据(比如当前登录的用户信息),常用的手段有:

1>. 方法传参:最直接,最安全。

2>. ThreadLocal:给每个线程开辟一个私有的“小储物柜”,线程之间互不看对方的东西。

3>. 数据库/缓存:将状态存在 Redis 或数据库中。

注:并发安全需要开发者自己保证,只要不在 Bean 里写成员变量,就是安全的



举报

© 著作权归作者所有


0