Spring事务
Spring的事务传播机制有哪些?
事务传播行为的本质就是一个带@Transactional的方法调用了另外一个带@Transactional的方法,并且是外部调用,不是类内部调用,然后Spring来决定是复用原有的事务,还是开启一个新的事务,或是不用事务这几种情况
REQUIRED(默认):思想就是已有事务就加入事务,没有就自己新开事务,能保证不会出现多个事务的情况,适合大多数场景
REQUIRES_NEW:思想就是无论原来是否有事务,自己都会新开事务,并且原来的事务会暂停执行等待这个事务完成,比较适合不要求强一致性的场景,比如日志记录
SUPPORTS:思想就是原来有事务就加入,没有自己也不开启
NOT_SUPPORTED :如果原来有事务,会暂停事务,自己执行的期间是无事务的,执行完成再恢复原来的事务
MANDATORY :会强制执行的两个方法都是有事务的,没有的话会报错 NEVER NEVER :绝对不会有事务,如果原来的方法也有事务,它会直接报错
NESTED :如果之前有事务,就在之前的事务里面嵌套事务,嵌套事务不影响父事务的回滚,但是父事务回滚会影响子事务回滚
常见的使用方式:
@Service
public class UserService {
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void fun2() {
// 逻辑
}
}
@Service
public class OrderService {
private UserService userService;
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void fun1() {
userService.fun2();
// 逻辑
}
}
一般选择的最多的就是REQUIRED,这样调用新方法的时候可以复用同一个事务,确保数据一致性
问:一个长的事务方法a,在读写分离的情况下,里面既有读库操作,也有写库操作,再调用个读库方法b,方法b该用什么传播机制呢?
要分情况,其实就是看你的这个读操作是放在什么地方,以及它的失败是否需要你对前面的操作进行回滚
假设这个b是在最后调用,然后出现异常了,这个时候如果是对前面没影响的,可以使用NOT_SUPPORTED,但是反过来,如果你这一步是对前面操作的校验查询呢,就一定要使用REQUIRED
假设在中间位置同理。
主要就是看你的这个报错是否希望回滚
Spring的事务在多线程下生效吗?为什么?@Transactional的原理?
主要看你是哪一种,有声明式和编程式,我们说的多线程事务失效一般式声明式的
@Transactional 在多线程环境下是会失效的,主要就是因为@Transactional是通过ThreadLocal来缓存事务的上下文,但是ThreadLocal又是线程隔离的,所以会失效
那如果我们确实有把任务分配给子线程去执行的场景,那确实可以考虑编程式事务
@Service
public class CrossThreadTransactionService {
@Autowired
private PlatformTransactionManager txManager;
@Autowired private OrderMapper orderMapper;
@Autowired private StockMapper stockMapper;
// 主线程协调多线程事务
public void crossThreadOperation(Long goodsId, Long userId) throws InterruptedException {
// 存储子线程的事务状态和执行结果
Map<Thread, Pair<TransactionStatus, Boolean>> threadTxMap = new ConcurrentHashMap<>();
// 子线程1:处理订单(独立事务)
Thread thread1 = new Thread(() -> {
// 1. 子线程1创建独立事务
TransactionDefinition def1 = new DefaultTransactionDefinition();
TransactionStatus tx1 = txManager.getTransaction(def1);
try {
// 子线程1的业务操作
orderMapper.createOrder(userId, goodsId);
threadTxMap.put(Thread.currentThread(), Pair.of(tx1, true));
} catch (Exception e) {
threadTxMap.put(Thread.currentThread(), Pair.of(tx1, false));
}
});
// 子线程2:处理库存(独立事务)
Thread thread2 = new Thread(() -> {
// 2. 子线程2创建独立事务
TransactionDefinition def2 = new DefaultTransactionDefinition();
TransactionStatus tx2 = txManager.getTransaction(def2);
try {
// 子线程2的业务操作
stockMapper.reduceStock(goodsId);
threadTxMap.put(Thread.currentThread(), Pair.of(tx2, true));
} catch (Exception e) {
threadTxMap.put(Thread.currentThread(), Pair.of(tx2, false));
}
});
// 启动子线程并等待执行完毕
thread1.start();
thread2.start();
thread1.join();
thread2.join();
// 主线程统一处理提交/回滚(核心:编程式的手动控制能力)
boolean allSuccess = threadTxMap.values().stream().allMatch(Pair::getValue);
if (allSuccess) {
// 所有子线程成功 → 逐个提交事务
threadTxMap.values().forEach(pair -> txManager.commit(pair.getKey()));
System.out.println("所有子线程事务提交成功");
} else {
// 有子线程失败 → 逐个回滚事务
threadTxMap.values().forEach(pair -> txManager.rollback(pair.getKey()));
System.out.println("存在子线程失败,所有事务回滚");
}
}
}
本质上就是用Map记录下每个子线程执行的情况,最终汇总各个子线程的情况来决定是提交还是回滚
但是可以发现异常是需要在主线程进行等待统一处理的,所以不是真正的解决多线程环境下事务失效的方式,这里相当于取巧了一下
这种就是协调式方案,适合各个执行逻辑不在同一个类的情况
但是,如果是扩库的情况就不行,我们在一个模块只能操作这个模块绑定的数据库的事务
Spring中如何开启事务?
主要有声明式和编程式两种
声明式就是直接在方法上加上@Transactional就行
public class UserService {
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void test() {}
}
主要就是通过注解声明,不过不要忘记声明捕获异常回滚的类型
此外就是声明事务的传播机制,一般按照默认的REQUIRED就行了
编程式事务:
可以使用PlatformTransactionManager
public class UserService {
@Autowired
private PlatformTransactionManager txManager;
public void test() {
// 创建事务规则,比如事务的传播行为
TransactionDefinition def = new DefaultTransactionDefinition();
// 开启事务并获取事务状态
TransactionStatus status = txManager.getTransaction(def);
// 修改事务状态为失败
status.setRollbackOnly();
// 提交事务
txManager.commit(status);
}
}
或是:TransactionTemplate
public class UserService {
@Autowired
protected TransactionTemplate transactionTemplate;
public void test() {
transactionTemplate.execute(status -> {
// 执行业务
// 如果想要回滚的话可以采用抛出异常或是手动标记回滚
// status.setRollbackOnly();
// throw new RuntimeException();
return null;
});
}
}
声明式事务的不足:
- 粒度大,至少是方法级别的
-
- 可能我们会忽略它,导致往里面写入一下RPC调用或是文件写入等耗时长且不可被回滚的操作
- 容易失效
-
- 事务是基于AOP实现的,在一些场景可能会导致失效:
-
-
- @Transactional应用在非 public 修饰的方法上
- @Transactional注解属性 propagation(传播行为) 设置错误
- @Transactional注解属性 rollbackFor 设置错误
- 同一个类中方法调用,导致@Transactional失效
- 异常被catch捕获导致@Transactional失效
- 数据库引擎不支持事务
- Bean没有被Spring管理,而是自己new了一个Bean
-
Spring中事务失效可能是哪些原因?
那我们这里就主要讲的是@Transactional失效
-
@Transactional加载了非public方法上
public class MyService {
@Transactional
private void doInternal() {
System.out.println("Doing internal work...");
}
}
这种情况会失效,因为AOP如果是JDK动态代理的话,只能带来public方法,官方也规定了只有public方法,事务才会生效
-
方法内部调用
public class MyService {
public void doSomething() {
doInternal(); // 自调用方法
}@Transactional public void doInternal() { System.out.println("Doing internal work..."); }}
方法内部调用时,使用的是this,没有使用代理对象,所以失效
- final、static方法
这些方法因为无法被覆盖重写或者不属于对象,所以都属于动态对象摸不到的范围,事务会失效
- 没有代理
如果你这个类压根没有被Spring管理,就不会有代理对象,就不会生效
- 参数设置错误
比如你指定的回滚类型没有覆盖触发的异常,就不会回滚
- 异常被捕获,没有抛出来
这种情况下也会失效
Spring中的事务事件如何使用?
这个其实就是问,如何去使用Spring提供的事件发布机制
事件发布的原理很简单,就是需要你发布一个事件,同时需要有这个事件的监听者
那采取的方式一般就是model关联,也就是根据我们发布的这个事件,去找到它的监听器,监听的方式就是这个方法出现在它的参数,说了可以有点模糊,直接看代码理解
发布事件
@Service
public class UserService {
@Resource
private ApplicationEventPublisher applicationEventPublisher; // 事件发布者
@Transactional
public void register() {
// 注册逻辑
// 发布注册事件
UserRegisterEvent userRegisterEvent = new UserRegisterEvent();
userRegisterEvent.setName("lin pei ji");
applicationEventPublisher.publishEvent(userRegisterEvent);
System.out.println("注册事件发布成功" + ",当前执行的线程是:" + Thread.currentThread().getId());
}
}
我们这里首先就是注入了Spring提供的一个ApplicationEventPublisher,事件发布器
然后我们会发布一个事件applicationEventPublisher.publishEvent(userRegisterEvent);
那这个事件的监听方的定义很简单
首先我们还是先把这个事件创建出来,说白了就是一个实体类
public class UserRegisterEvent {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
然后你需要定义这个事件的监听方法
@Component
public class UserRegisterEventListener {
@TransactionalEventListener
public void onReceive(UserRegisterEvent event) {
System.out.println("监听到注册事件" + ",用户是:" + event.getName() +
",当前执行的线程是:" + Thread.currentThread().getId());
}
}
判定当前方法是否是该事件的监听方法的两个指标
- 加了@TransactionalEventListener注解
- 该方法包含该事件实体,并且只包含这一个参数
注意:一定是只包含一个参数,多了就失效了
总结一下就是:通过 @TransactionalEventListener定义方法作为某一个事件的监听者
默认情况下,Spring提供的这个事件监听机制是同步的,如果要异步,就自己创建一个线程单独去跑
然后这个事件监听器还可以指定是在事务的什么阶段触发
@Component
public class UserRegisterEventListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onReceive(UserRegisterEvent event) {
System.out.println("监听到注册事件" + ",用户是:" + event.getName() +
",当前执行的线程是:" + Thread.currentThread().getId());
}
}
BEFORE_COMMIT 事务提交前(未最终落库)
AFTER_COMMIT 事务成功提交后(默认值)
AFTER_ROLLBACK 事务回滚后
AFTER_COMPLETION 事务完成后(提交 / 回滚都触发)
Spring IOC与容器基础
介绍一下Spring的IOC
IOC 就是控制反转
如果没有IOC的时候,对象的创建就是由我们的程序来控制的,但是有了IOC之后,不需要在程序中创建对象,IOC会自动帮我们创建,并将对象在我们需要的地方进行注入
总结:原来的对象创建的控制权是在我们的程序中,有了IOC,控制权交给了IOC,所以叫控制反转
IOC 只是一个思想,实现的方式有很多种,核心思想就是在启动初期,扫描对应的包路径,并将底下的对象创建出来放到容器里面,在需要使用的地方进行注入
IOC的优点:
- 解耦,使用方无需关注Bean内部的创建现细节,只需要使用就完了
- 减少重复创建逻辑,避免浪费,大多数情况下,Bean只需要创建一份,重复使用即可,有了IOC,可以实现单例创建,重复利用
- Bean的修改更加灵活,使用方无需感知,传统的方式中,假设一个对象依赖了其它对象,A依赖B,变成A依赖C,这种依赖修改,使用方需要同步改进,但是在IOC下,使用方无需管理这一部分
Spring 的 IOC 实现方式:

在容器启动的时候,根据每个bean的要求,将bean注入到SpringContainer(容器)中。如果有其它的bean需要使用,就直接从容器中进行获取即可
实例代码:
ApplicationContext context= new AnnotationConfigApplicationContext("cn.wxxlamp.spring.ioc");
Bean bean = context.getBean(Bean.class);
bean.use();
执行的时候会扫描我们指定的 cn.wxxlamp.spring.ioc 这个包,找出这个包下需要被管理的类
- 主要就是扫描 @Component/@Service/@Controller/@Repository 的类,此外如果扫描到 加了 @Configuration 的类也会解析它里面加了@Bean的方法
- 将扫描出来的类创建Bean,具体流程:
-
- 生成BeanDefinition:就是每一个类的Bean说明书,记录类名和作用域,依赖关系之类的
- 注册BeanDefinition:把这个说明书放入bean容器的注册表
- 实例化Bean:通过反射创建类的实例
- 依赖注入:如果当前类有依赖了其它的Bean对象,也会一并创建并注入进来,这里注入的方式有 @Autowired/构造器注入/Setter注入
- 初始化Bean:执行@PostConstruct标注的方法,也就是初始化方法(不一定有)
- 缓存Bean,将创建好之后的Bean缓存到单例池,后续可以通过context.getBean() 进行获取
BeanFactory和FactroyBean的关系?
BeanFactory 其实就是Bean容器的一部分,说白了就是一个接口,主要是提供一系列获取Bean和操作Bean的方法,我们可以很方便的获取到指定类的Bean对象的信息
像我们用的最多的ApplicationContext获取Bean,其实ApplicationContext就实现了BeanFactory,底层就是通过它实现的
从Bean容器获取指定的Bean对象的方式:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args);
UserService service = applicationContext.getBean(UserService.class);
System.out.println(service.get());
}
}
@Component
public class UserService {
int get(){return 1;}
}
BeanFactory的核心源码:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.beans.factory;
import org.springframework.beans.BeansException;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);
<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);
boolean containsBean(String name);
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;
String[] getAliases(String name);
}
总结:BeanFactory的作用其实就是提供一个操作Bean的统一入口
FactoryBean是另外一个概念,它其实本身就是一个被Spring管理的Bean,但是它比较特殊,当Spring容器试图获取它的时候,并不是直接获取到它本身,而是它造出来的一个对象
简单来说:
/**
* @Author: Daylight
* @Date: 2026-03-15 15:20
*/
@Component
public class UserService implements FactoryBean<UserService> {
int a = 0;
String b = "";
int get(){return 1;}
// 真正要给使用者的对象
@Override
public UserService getObject() throws Exception {
UserService userService = new UserService();
userService.a = 100;
userService.b = "你好";
return userService;
}
// 返回对象的真正类型
@Override
public Class<?> getObjectType() {
return UserService.class;
}
// 是否是单例
@Override
public boolean isSingleton() {
return true;
}
@Override
public String toString() {
return "UserService{" +
"a=" + a +
", b='" + b + '\'' +
'}';
}
}
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(DemoApplication.class, args);
UserService service = applicationContext.getBean(UserService.class);
System.out.println(service);
}
}
UserService{a=100, b='你好'}
那到这里也可以知道,当我们试图通过BeanFactory获取一个类的Bean的时候,如果这个类恰好实现了FactoryBean接口,那么我们拿到的就不再是这个类原本的Bean对象,而是调用它的getObject方法返回的对象
这样的好处是我们可以把很多复杂的增强逻辑写到这里面,使用方无需自己通过复杂的方式去实现,只要调用就行
例如像Dubbo的@DubboReference,底层其实就用到了这一块相关的内容
总结:FactoryBean其实就是生产Bean的一个工具(同时它自己也是个Bean),相当于一个标识,当一个类实现了这个接口,那我们拿到的就不是原来的Bean,而是它的getObject() 增强后的对象
@PostConstruct、init-method和afterPropertiesSet执行顺序
PostConstruct、init-method和afterPropertiesSet这三个都是在Bean初始化阶段执行,要是没有这几个的话,初始化阶段就直接跳过去执行初始化后置逻辑
构造方法则是初始化阶段之前,也就是更早的实例化阶段
执行顺序:构造函数>@PostConstruct > afterPropertiesSet > init-method
@PostConstruct 用于在构造函数执行完成之后,并且依赖注入完成之后执行,一般加载方法上面用来执行特定的初始化方法,在Bean初始化阶段执行
@Component
public class Test {
public Test() {
System.out.println(222);
}
@PostConstruct
void init() {
System.out.println(123);
}
}
222
123
需要交给Spring容器管理才可以
init-method 其实和@PostConstruct 的作用是差不多的,就是告诉Spring在完成构造器执行+依赖注入之后执行的指定方法
此外因为init-method 其实是写在XML文件里面的,如果不是XML文件的话,也可以使用@Bean注解里面的initMethod 属性来来执行初始化方法
和@PostConstruct 的区别就是,@PostConstruct 是注解式的,init-method/initMethod是配置式的
// 普通的业务类(无任何Spring注解,纯POJO)
public class OrderService {
// 构造器(实例化阶段执行)
public OrderService() {
System.out.println("1. OrderService构造器执行");
}
// 自定义的初始化方法(名字任意,比如init、initOrder、start都可以)
public void init() {
System.out.println("3. initMethod指定的初始化方法执行");
// 这里写初始化逻辑:比如加载订单缓存、初始化MQ连接等
loadOrderCache();
}
private void loadOrderCache() {
System.out.println("订单缓存加载完成");
}
}
// 配置类(用@Bean注册Bean,并指定initMethod)
@Configuration
public class AppConfig {
// 注册OrderService为Spring Bean,并指定初始化方法为"init"
@Bean(initMethod = "init")
public OrderService orderService() {
return new OrderService();
}
}
// 启动类测试
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
// 获取Bean(触发初始化流程)
OrderService orderService = context.getBean(OrderService.class);
}
}
1. OrderService构造器执行
3. initMethod指定的初始化方法执行
订单缓存加载完成
就是在构造方法执行完成之后,会执行initMethod指定的方法
这里是先根据方法返回值类型的Bean反向定位到具体的类,执行指定的方法
afterPropertiesSet是 Spring 的 InitializingBean 接口中的方法。如果一个 Bean 实现了 InitializingBean 接口,Spring 在初始化阶段会调用该接口的 afterPropertiesSet 方法。
@SpringBootApplication
public class DemoApplication implements InitializingBean {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
System.out.println("222");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("111");
}
}
111
222
Spring中shutdownhook作用是什么?
就是Spring像JVM注册的关闭钩子函数,我们可以在里面编写程序关闭时要处理的逻辑
几种使用方式:
-
继承DisposableBean,实现destroy方法
@Component
public class Test implements DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("清理工作");
}
} -
通过@PreDestroy注解
@Component
public class Test {@PreDestroy public void clean() { System.out.println("清理工作"); }}
-
基于Spring的事件监听器来监听ContextClosedEvent事件并进行处理
@Component
public class Test implements ApplicationListener<ContextClosedEvent> {@Override public void onApplicationEvent(ContextClosedEvent event) { System.out.println("清理工作"); }}
无论哪种都是在程序要关闭之前调用执行的清理逻辑
其实就算你不主动注册优雅关闭,Spring默认也会注册的
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
shutdownHook.registerApplicationContext(context);
}
this.refresh(context);
}
然后我们没有办法直接调用Spring的shutdownhook,但是可以通过我上面列出的几种方式去间接实现
总结一下就是shutdownhook其实就是在程序彻底关闭之间做的一些清理工作,包括对Bean容器的清理,底层依然时通过JVM原生的addShutdownHook做的一些增强
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println(111);
}));
Spring MVC
什么是MVC
MVC是一种软件设计架构
分别对应:模型层、视图层、控制层
具体的思想就是:
- 模型(Model):表示应用程序的核心业务逻辑和数据。模型通常包含一些数据结构和逻辑规则,用于处理数据的输入、输出、更新和存储等操作。模型并不关心数据的显示或用户的交互方式,它只关注数据本身以及对数据的操作。
- 视图(View):表示应用程序的用户界面,用于显示模型中的数据。视图通常包含一些控件和元素,用于展示数据,并且可以与用户进行交互。视图并不关心数据的处理或存储方式,它只关注如何呈现数据以及如何与用户进行交互。
- 控制器(Controller):表示应用程序的处理逻辑,用于控制视图和模型之间的交互。控制器通常包含一些事件处理和动作触发等操作,用于响应用户的输入或视图的变化,并对模型进行操作。控制器通过将用户的输入转化为对模型的操作,从而实现了视图和模型之间的解耦。
MVC模式的核心思想是 将应用程序的表示和处理分离开 来,从而使得应用程序更加灵活、易于维护和扩展。这种模式可以提高代码的可读性和可维护性 ,同时也可以促进代码的复用和分工,使得多人协作开发变得更加容易。
SpringMVC是如何将不同的Request路由到不同Controller中的?
启动阶段: SpringMVC 启动时,扫描@RequestMapping(含衍生注解如@GetMapping),将注解信息封装为RequestMappingInfo(聚合 URL、请求方法、请求头、参数等匹配条件),与对应的 HandlerMethod 绑定后注册到MappingRegistry
请求阶段:
- 解析 Request 的 URL、请求方法(GET/POST)、Header 等信息,通过
lookupHandlerMethod方法从MappingRegistry中匹配RequestMappingInfo; - 若匹配到多个 HandlerMethod,通过
RequestMappingInfo的各类RequestCondition(组合模式)比较出最优匹配; - 最终返回匹配的 HandlerMethod,完成路由;
设计模式: RequestMappingInfo聚合各类匹配条件体现门面模式,RequestCondition的组合匹配体现组合模式。
MVC请求流程:
- 请求入口:Tomcat 将请求交给 DispatcherServlet(继承 FrameworkServlet),核心执行
doDispatch方法; - 核心步骤:
-
- 【HandlerMapping】根据 Request 获取
HandlerExecutionChain(含 HandlerMethod + 拦截器); - 【HandlerAdapter】适配 HandlerMethod,执行拦截器
preHandle(责任链模式); - 【参数解析】通过
HandlerMethodArgumentResolver解析 Controller 方法参数,反射调用目标方法; - 【返回值处理】通过
HandlerMethodReturnValueHandler处理方法返回值,封装为ModelAndView; - 【拦截器后置】执行拦截器
postHandle; - 【视图渲染】对
ModelAndView进行 render,将结果写入 Response; - 【异常处理】通过
HandlerExceptionResolver匹配@ExceptionHandler注解方法处理异常,逻辑与 HandlerMethod 执行一致;
- 【HandlerMapping】根据 Request 获取
- 设计模式:拦截器执行体现责任链模式,参数 / 返回值解析体现适配器 + 策略模式。
Spring Boot核心
SpringBoot和Spring的区别是什么?
SpringBoot 其实就是封装了Spring以及它的生态的一个脚手架
主要的区别是:
- 将很多的配置直接帮我们搞定,我们只需要依赖它提供好的start,就可以把相关的东西都引入,大大的降低我们开发成本
- 内置了TomCat服务器,我们无需额外配置这一块的东西,直接运行就相当于一个服务器
- 约定大于配置,大家都遵循一定的约定进行开发,能降低我们的配置工作,比如yml文件的读取,以及业务类和启动器的目录层级关系
SpringBoot的启动流程是怎么样的?
从一个启动类入手
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
run的实现细节:
public static ConfigurableApplicationContext run(Object source, String... args) {
return run(new Object[] { source }, args);
}
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}
分成 new SpringApplication() 和 .run(args);
new SpringApplication()
在构造方法中调用 initialize() 实现了初始化的逻辑

public SpringApplication(Object... sources) {
initialize(sources);
}
private void initialize(Object[] sources) {
// 添加源:如果 sources 不为空且长度大于 0,则将它们添加到应用的源列表中
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
// 设置web环境:推断并设置 Web 环境(例如,检查应用是否应该运行在 Web 环境中)
this.webEnvironment = deduceWebEnvironment();
// 加载初始化器:设置 ApplicationContext 的初始化器,从 'spring.factories' 文件中加载 ApplicationContextInitializer 实现
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置监听器:从 'spring.factories' 文件中加载 ApplicationListener 实现
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 确定主应用类:通常是包含 main 方法的类
this.mainApplicationClass = deduceMainApplicationClass();
}
- 添加源:将提供的源(通常是配置类)添加到应用的源列表中。
- 设置 Web 环境:判断应用是否应该运行在 Web 环境中,这会影响后续的 Web 相关配置。
- 加载初始化器:从
spring.factories文件中加载所有列出的ApplicationContextInitializer实现,并将它们设置到SpringApplication实例中,以便在应用上下文的初始化阶段执行它们。 - 设置监听器:加载和设置
ApplicationListener实例,以便应用能够响应不同的事件。 - 确定主应用类:确定主应用类,这个主应用程序类通常是包含
public static void main(String[] args)方法的类,是启动整个Spring Boot应用的入口点。
其中第三步,加载初始化器就是SpringBoot自动配置的核心,这一步就是将spring.factories文件加载并实例化好指定的类

从spring.factories中加载指定类型的所有类的具体方法:
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
// 获取当前线程的上下文类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 从spring.factories加载指定类型的工厂名称,并使用LinkedHashSet确保名称的唯一性,以防重复
Set<String> names = new LinkedHashSet<String>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 创建指定类型的实例。这里使用反射来实例化类,并传入任何必要的参数
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 对实例进行排序,这里使用的是Spring的注解感知比较器,可以处理@Order注解和Ordered接口
AnnotationAwareOrderComparator.sort(instances);
// 返回实例集合
return instances;
}
总的来看就是:

SpringApplication.run()
这个方法就是SpringBoot启动的核心

整个源码流程:
public ConfigurableApplicationContext run(String... args) {
// 创建并启动一个计时器,用于记录应用启动耗时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
// 配置无头(headless)属性,影响图形环境的处理
configureHeadlessProperty();
// 获取应用运行监听器,并触发开始事件
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 创建应用参数对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 准备环境,包括配置文件和属性源
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 打印应用的 Banner
Banner printedBanner = printBanner(environment);
// 创建应用上下文
context = createApplicationContext();
// 创建失败分析器
analyzers = new FailureAnalyzers(context);
// 准备上下文,包括加载 bean 定义
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新上下文,完成 bean 的创建和初始化
refreshContext(context);
// 刷新后的后置处理
afterRefresh(context, applicationArguments);
// 通知监听器,应用运行完成
listeners.finished(context, null);
// 停止计时器
stopWatch.stop();
// 如果启用了启动信息日志,记录应用的启动信息
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//触发ApplicationStartedEvent 事件
listeners.started(context);
//调用实现了 CommandLineRunner 和 ApplicationRunner 接口的 bean 中的 run 方法
callRunners(context, applicationArguments);
// 触发 ApplicationReadyEvent 事件
listeners.running(context);
// 返回配置好的应用上下文
return context;
}
catch (Throwable ex) {
// 处理运行失败的情况
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
看到这里其实可以发现步骤很多,完全理不清楚
其实不需要看那么多,直接从大的范围入手
整个流程就是:
- 【初始化阶段】:先创建SpringApplication对象,做基础准备
-
- 识别应用类型(是Web项目还是普通项目);
- 加载初始化器(ApplicationContextInitializer)和监听器(ApplicationListener);
- 找到main方法所在的主类(启动类)。
- 【容器启动阶段】:核心是创建并刷新Spring容器(ApplicationContext)
-
- 启动内嵌的Web服务器(比如Tomcat,这也是SpringBoot不用手动部署的原因);
- 加载自动配置类(SpringBoot自动配置的核心,比如自动配数据源、MVC);
- 扫描启动类所在包下的Bean(@Controller/@Service等),注册到容器;
- 执行Bean的初始化(比如@PostConstruct、初始化方法)
- 【启动完成阶段】:容器刷新完成后,发布启动成功事件
-
- 触发所有启动完成的监听器(比如自定义的启动后逻辑);
- 控制台打印启动日志(比如Tomcat启动端口、启动耗时)。
注意顺序问题:先启动tomcat,在扫描Bean
Springboot是如何实现自动配置的?
首先就是启动类的@SpringBootApplication注解包含了@EnableAutoConfiguration注解,这个注解的作用就是开启自动配置的开关
@EnableAutoConfiguration 注解会去读取一堆自动配置类,比如tomcat配置以及数据源等,这个配置类定义的位置在不同版本是不一样的。旧版本读spring.factories文件,新版本读AutoConfiguration.imports文件
当然加载的时候,这些自动配置类不会全部生效,会通过Spring的条件注解(比如@ConditionalOnClass:有某个类才生效、@ConditionalOnMissingBean:没有自定义Bean才生效)判断------比如项目里引入了MySQL驱动,才会自动配置数据源;如果自己写了数据源配置,自动配置就会失效,保证"自定义优先"。

其实2.7以后主要就是加载AutoConfiguration.imports 另外一个是历史遗留
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({JdbcUtils.class})
@ConditionalOnMissingBean({Flyway.class})
@EnableConfigurationProperties({FlywayProperties.class})
public static class FlywayConfiguration {
public FlywayConfiguration() {
}
@Bean
ResourceProviderCustomizer resourceProviderCustomizer() {
return new ResourceProviderCustomizer();
}
/** @deprecated */
@Deprecated(
since = "3.0.0",
forRemoval = true
)
public Flyway flyway(FlywayProperties properties, ResourceLoader resourceLoader, ObjectProvider<DataSource> dataSource, ObjectProvider<DataSource> flywayDataSource, ObjectProvider<FlywayConfigurationCustomizer> fluentConfigurationCustomizers, ObjectProvider<JavaMigration> javaMigrations, ObjectProvider<Callback> callbacks) {
return this.flyway(properties, resourceLoader, dataSource, flywayDataSource, fluentConfigurationCustomizers, javaMigrations, callbacks, new ResourceProviderCustomizer());
}
}
SpringBoot如何让你的bean在其他bean之前加载?
Bean的初始化时期有两个:
- Spring容器主动去初始化该Bean
- 其他Bean依赖该Bean,该Bean会先被初始化
所以实现的方式主要有:
-
直接依赖某一个Bean
@Component
public class A {@Autowired private B b;}
这种是在要确定两个bean的执行顺序的时候,这里这样做,不过前提是这两个类都是我们自己定义的
-
@DependsOn
@Component
public class BeanConfig {@Bean @DependsOn("user") public Test getTestBean() { return new Test(); }}
这个就是在加载某一个Bean的时候指定这个Bean的创建需要依赖某一个Bean,这样子被依赖的就会提前创建,
比较适合有第三方类,我们无法直接修改的情况
@DependsOn注解也可以作用在@Component注解上面
-
BeanFactoryPostProcessor
@Component
public class User implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
User bean = beanFactory.getBean(User.class);
}
}
这个是用来让某一个Bean在所有Bean的初始化之前进行初始化
原理:
Spring 容器启动流程(核心步骤):
1. 加载 Bean 的定义信息(扫描 @Component、@Bean 等,把 Bean 的"蓝图"记下来,还没创建实例);
2. 执行所有 BeanFactoryPostProcessor 的方法(修改 Bean 的定义信息);
3. 执行 BeanPostProcessor 的前置方法(Bean 实例化前的预处理);
4. 创建 Bean 实例(初始化 Bean);
5. 执行 BeanPostProcessor 的后置方法(Bean 实例化后的处理);
6. Bean 初始化完成,放入容器。
此外有一个注解,可能会造成一定的误解
@Component
public class Container {
private final List<Bean> beanList;
public Container(List<Bean> beanList) {
this.beanList = beanList;
}
}
@Order(1)
@Component
class BeanA implements Bean {}
@Order(2)
@Component
class BeanB implements Bean {}
Order只能控制同一个Bean类型中集合的顺序,不能控制不同Bean的初始化顺序
SpringBoot如何做优雅停机?
优雅停机就是说当我们的程序停止之后,会停止接收请求,但是不会马上关闭,而是等待请求都处理完才关闭
Spring Boot 2.3+ 实现优雅停机很简单,核心就两点:
- 基础启用:配置文件加
server.shutdown=graceful,应用关闭时会拒绝新请求、等待正在处理的请求完成(默认等30秒,可通过spring.lifecycle.timeout-per-shutdown-phase自定义时长); - 主动触发:引入Actuator依赖,配置暴露shutdown端点后,发送POST请求到/actuator/shutdown就能主动触发优雅停机。
优雅停机的核心是保证应用关闭时不丢数据、不中断正在处理的请求。
SpringBoot是如何实现main方法启动Web项目的?
本质就是启动了内置的tomcat服务器
它的一个整体的流程就是:
启动类调用了 run() 方法 ------> 调用 refreshContext() ------> refresh() ------> onRefresh() ------> createWebServer()
核心的逻辑就是在createWebServer()
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
StartupStep createWebServer = getApplicationStartup().start("spring.boot.webserver.create");
ServletWebServerFactory factory = getWebServerFactory();
createWebServer.tag("factory", factory.getClass().toString());
this.webServer = factory.getWebServer(getSelfInitializer());
createWebServer.end();
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}

调用到 this.webServer = factory.getWebServer(getSelfInitializer()); 的时候,factor会有三种实现,tomcat/jetty/undertow,默认就是tomcat
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
for (LifecycleListener listener : this.serverLifecycleListeners) {
tomcat.getServer().addLifecycleListener(listener);
}
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}
我们点进tomcat的实现,可以看到,首先就是创建一个tomcat
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, this.getPort() >= 0, this.getShutdown());
}
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
this.monitor = new Object();
this.serviceConnectors = new HashMap();
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
this.gracefulShutdown = shutdown == Shutdown.GRACEFUL ? new GracefulShutdown(tomcat) : null;
this.initialize();
}
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false));
synchronized(this.monitor) {
try {
this.addInstanceIdToEngineName();
Context context = this.findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && "start".equals(event.getType())) {
this.removeServiceConnectors();
}
});
this.tomcat.start();
this.rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(), this.getClass().getClassLoader());
} catch (NamingException var5) {
}
this.startDaemonAwaitThread();
} catch (Exception var6) {
this.stopSilently();
this.destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", var6);
}
}
}
很不容易,终于在对tomcat的initialize() 中,调用了start() 进行启动
整个流程,我们可以总结,首先就是在run() 方法里面显式进行上下文刷新(调用refreshContext() ),在这个方法里面有 refresh(),我们追进去,会经历onRefresh()和createWebServer(),在createWebServer() 中会根据不同的web服务器实现创建不同的服务器,默认就是tomcat,此外还有undertow和jetty
假设我们的实现是tomcat,那它其实就是创建一个tomcat,并调用initialize() 进行初始化以及调用start() 实现真正启动
注意:bean的初始化是在tomcat加载之后,bean的加载就是注册一个 BeanPostProcessor ,这个是Bean的初始化器
如何自定义一个starter?
-
首先就是先引入SpringBoot依赖
-
创建一个一个类,并且用 @ConfigurationProperties 来绑定配置文件中的属性
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
-
@author Hollis
*/
@ConfigurationProperties(prefix = XxlJobProperties.PREFIX)
public class XxlJobProperties {public static final String PREFIX = "spring.xxl.job";
private boolean enabled;
private String adminAddresses;
private String accessToken;
private String appName;
private String ip;
private int port;
private String logPath;
private int logRetentionDays = 30;
//getter setter
}
-
它会在你的配置application.yml里面去绑定属性
-
创建一个类把一些前置的Bean创建出来
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/**
-
@author Hollis
*/
@Configuration
@EnableConfigurationProperties(XxlJobProperties.class)
public class XxlJobConfiguration {private static final Logger logger = LoggerFactory.getLogger(XxlJobConfiguration.class);
@Autowired
private XxlJobProperties properties;@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = XxlJobProperties.PREFIX, value = "enabled", havingValue = "true")
public XxlJobSpringExecutor xxlJobExecutor() {
logger.info(">>>>>>>>>>> xxl-job config init.");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(properties.getAdminAddresses());
xxlJobSpringExecutor.setAppname(properties.getAppName());
xxlJobSpringExecutor.setIp(properties.getIp());
xxlJobSpringExecutor.setPort(properties.getPort());
xxlJobSpringExecutor.setAccessToken(properties.getAccessToken());
xxlJobSpringExecutor.setLogPath(properties.getLogPath());
xxlJobSpringExecutor.setLogRetentionDays(properties.getLogRetentionDays());
return xxlJobSpringExecutor;
}
}
-
-
将加了 @Configuration 的类写入到下面的目录进行管理

只有创建Bean的类需要加上 @Configuration 注解,其它的比如业务类或是绑定配置属性的都不需要
为什么SpringBoot3中移除了spring.factories
SpringBoot 3 移除spring.factories核心是为了适配云原生:
- spring.factories依赖运行时反射扫描自动配置类,启动慢、开销大,不兼容GraalVM原生镜像编译;
- 新的AutoConfiguration.imports文件结合@AutoConfiguration注解,通过APT编译期处理注解,能提前确定配置类,提升启动速度、降低内存消耗,契合云原生需求;
- 实际开发中只需用AutoConfiguration.imports文件指定自动配置类路径即可替代spring.factories。
Spring6.0和SpringBoot3.0有什么新特性?
主要就是:(都是为了适配云原生的特点)
- 引入了AOT,预先编译,传统的JIT是把热点代码在运行期编译成机器码缓存起来,但是这通常需要启动之后很长一段时间去检测热点代码;而AOT就是在程序运行前,把代码都编译成机器码,能大大的提高整体的运行效率
- 引入了Spring Native,这个就是可以基于GraalVM 把程序打包成镜像,更加的适配云原生
Spring进阶与实战
Spring在业务中常见的使用方式
-
实现策略模式:
/**
-
@Author: Daylight
-
@Date: 2026-03-15 21:30
*/
@Component
public class UserServiceFactory {
@Autowired
private List<TsetService> list;@Autowired
private Set<TsetService> set;@Autowired
private Map<String, TsetService> map;@Autowired
private TsetService[] arr;@PostConstruct
public void init() {
System.out.println(list);
System.out.println(set);
System.out.println(map);
System.out.println(arr.length);
System.out.println(arr);
}
}
[TsetServiceImpl1{}, TsetServiceImpl2{}, TsetServiceImpl3{}]
[TsetServiceImpl1{}, TsetServiceImpl2{}, TsetServiceImpl3{}]
{tsetServiceImpl1=TsetServiceImpl1{}, tsetServiceImpl2=TsetServiceImpl2{}, tsetServiceImpl3=TsetServiceImpl3{}}
3
[Lcom.example.demo.testtt.service.TsetService;@17ae98d7 -
就是用@Autowired加在map上
-
通过AOP实现拦截
@Target({ ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ParamsCheck {
boolean ignore() default false;
}@Aspect
@Component
public class ValidateAspect {@Around("@annotation(com.hollis.annotation.ParamCheck)") public void ParamCheckAround(JoinPoint joinPoint) throws Throwable { // 判断是否需要校验 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); ParamsCheck paramsCheckAnnotation = method.getAnnotation(ParamsCheck.class); if (paramsCheckAnnotation != null && paramsCheckAnnotation.ignore()) { return joinPoint.proceed(); } Object[] objects = joinPoint.getArgs(); for (Object arg : objects) { if (arg == null) { break; } // 校验参数,失败抛出异常 } }}
-
通过事件监听发布实现解耦
public class UserRegisterEvent extends ApplicationEvent {
private String userName; // source 就是标识这个事件是谁发出的,一般用this就行 public UserRegisterEvent(Object source, String userName) { super(source); this.userName = userName; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; }}
@Component
public class UserRegisterEventListener {@EventListener(UserRegisterEvent.class) public void onReceive(UserRegisterEvent event) { System.out.println("123" + event.getUserName()); }}
@Service
public class UserService {@Resource private ApplicationEventPublisher applicationEventPublisher; public void register() { // 业务 // 发布事件 UserRegisterEvent event = new UserRegisterEvent(this, "张三"); applicationEventPublisher.publishEvent(event); }}
-
事务管理
@Service
public class OrderService {
private UserService userService;
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void fun1() {
userService.fun2();
// 逻辑
}
}
在Spring中如何使用SpringEvent做事件驱动
public class UserRegisterEvent extends ApplicationEvent {
private String userName;
// source 就是标识这个事件是谁发出的,一般用this就行
public UserRegisterEvent(Object source, String userName) {
super(source);
this.userName = userName;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
@Component
public class UserRegisterEventListener {
@EventListener(UserRegisterEvent.class)
public void onReceive(UserRegisterEvent event) {
System.out.println("123" + event.getUserName());
}
}
@Service
public class UserService {
@Resource
private ApplicationEventPublisher applicationEventPublisher;
public void register() {
// 业务
// 发布事件
UserRegisterEvent event = new UserRegisterEvent(this, "张三");
applicationEventPublisher.publishEvent(event);
}
}
首先就是先定义事件,然后定义事件监听者,使用 @EventListener并指定监听事件类型
然后事件发布者使用ApplicationEventPublisher进行发布
Spring中如何实现多环境配置?
我们平常在开发的时候,会有多套环境,开发环境、测试环境、预发布环境以及生产环境,不同的环境中应用的配置可能不太一样
那我们一般的做法就是定义多份yml配置文件,并在主配置文件去指定要读取哪一份
多份配置文件的命名遵循 application-{环境名}.yml
然后主配置文件命名为 application.yml
# application.yml(主配置)
spring:
profiles:
active: dev # 默认激活dev,加载application-dev.yml
server:
port: 8080 # 通用配置:所有环境都用8080端口
主配置文件通过 spring.profiles.active 这个参数指定要激活哪一套
所以这也要求我们的不同环境的配置文件命名规则必需遵守application-{环境名}.yml
Spring中用到了哪些设计模式
代理模式:有些Bean是经过代理的对象,不是原对象
单例模式:Bean在容器中默认是单例的
模板方法:Spring有提高一些模板,比如事务模板TransactionTemplate
责任链模式:一个请求进来会经过层层解析处理