每日一记(8/27):Spring 核心原理与 RabbitMQ 可靠性复盘
Spring 单例 Bean 的线程安全
singleton 是 Spring 默认作用域,Bean 全局只有一个实例,本身不是线程安全的。但通常 Service、Dao 这类 Bean 没有可变状态(只有方法内的局部变量,栈上私有),所以从某种程度来说,这些单例 Bean 在实践中是线程安全的。
判断标准一句话:有没有共享的可变成员变量。如果 Bean 里定义了可修改的成员变量,多个线程并发读写就会有竞态问题,这时才需要考虑线程安全,解决方式有两种:
- 改作用域为多例(
prototype),每个线程拿独立实例; - 加锁(
synchronized/ReentrantLock)或改用并发容器同步访问。
事务失效的三个常见场景
@Transactional 不生效,通常是下面三个原因:
- 异常被自己 catch 吞掉了:方法内部 try/catch 了异常却没往外抛,Spring 感知不到异常,自然不触发回滚;
- 非 public 方法:事务基于 AOP 动态代理实现,非 public 方法无法被代理拦截,事务失效;
- 默认只回滚 unchecked 异常:Spring 默认只对
RuntimeException及其子类(unchecked)回滚,受检异常(checked)默认不回滚,需要显式加@Transactional(rollbackFor = Exception.class)来覆盖。
AOP:面向切面编程
把与业务无关、却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可复用的模块,命名为切面(Aspect)——核心目的是减少重复代码、降低模块间耦合、提升可维护性。
典型应用:记录操作日志、缓存处理,以及 Spring 内置的事务处理。
事务控制的两种方式:
- 编程式事务:在业务代码里手动调用事务 API(
TransactionTemplate等),对业务代码有侵入性,很少使用; - 声明式事务:对方法前后进行拦截,把事务处理编织到被拦截的方法中——在目标方法执行前开启事务,执行完成后根据结果决定提交或回滚。
Bean 的生命周期
- 通过 BeanDefinition 获取 Bean 的定义信息;
- 调用构造函数实例化 Bean;
- 进行 Bean 的依赖注入;
- 处理 Aware 接口(如
BeanNameAware、ApplicationContextAware); - 执行 BeanPostProcessor 的
postProcessBeforeInitialization; - 执行初始化方法(
InitializingBean#afterPropertiesSet或自定义init方法); - 执行 BeanPostProcessor 的
postProcessAfterInitialization(AOP 动态代理发生在这里); - 销毁 Bean。
动态代理的两种方式:
- JDK 代理:基于接口;
- CGLIB 代理:基于继承。
循环依赖
循环依赖一般通过三级缓存解决,三级缓存的核心是提供一个对象工厂(singletonFactories)来生成代理对象,从而提前暴露尚未创建完成的 Bean 引用。
但构造方法注入的循环依赖三级缓存也解决不了(构造器需要立即拿到完整依赖,无法提前暴露半成品),通常的处理方式是加 @Lazy 注解做延迟加载——什么时候真正需要了再加载。
SpringMVC 执行流程
老旧的视图阶段(JSP):
请求 → 前端控制器 DispatcherServlet → 查询 Handler → 处理映射器 HandlerMapping → 返回处理器执行链 HandlerExecutionChain → 前端控制器 → 执行 Handler → 处理器适配器 HandlerAdapter → 处理器 Handler → 返回 ModelAndView 给前端控制器 → 视图解析器 ViewResolver → 返回 View 对象 → 渲染视图(JSP)→ 返回请求。
前后端分离阶段:
前端控制器 → 查询 Handler → 处理器映射器 → 返回处理器执行链 → 执行 Handler → 处理器适配器(处理参数、处理返回值)→ 处理器 Handler(方法上添加了 @ResponseBody,通过 HttpMessageConverter 把返回结果转换为 JSON 并响应)。
SpringBoot 自动配置原理
@SpringBootApplication 封装了三个注解:
@SpringBootConfiguration@EnableAutoConfiguration(核心)@ComponentScan
其中 @EnableAutoConfiguration 通过 @Import(AutoConfigurationImportSelector.class) 加载导入对应的配置选择器,内部读取该项目及项目所引用 jar 包中 classpath 下的 META-INF/spring.factories 文件里配置类的全类名。这些配置类中定义的 Bean,会根据条件注解所指定的条件决定是否导入到 Spring 容器中。
条件判断会有像 @ConditionalOnClass 这样的注解——判断是否有对应的 class 文件,如果有则加载该类,把这个配置类里的所有 Bean 放进容器使用。
SpringCloud 五大组件
通常版本(Netflix 全家桶):
- 注册中心:Eureka
- 负载均衡:Ribbon
- 远程调用:Feign
- 服务熔断:Hystrix
- 网关:Zuul / Gateway
随着 SpringCloudAlibaba 在国内兴起,常用组合变成:
- 注册中心 / 配置中心:Nacos
- 负载均衡:Ribbon
- 服务调用:Feign
- 服务保护:Sentinel
- 服务网关:Gateway
注册中心
注册中心主要职责:
- 注册服务信息;
- 心跳续约;
- 拉取服务;
- 远程调用。
消息中间件:RabbitMQ
保证消息不丢失
为确保「双写一致性」:
- 开启生产者确认机制;
- 启用持久化功能,需要对交换机、队列和消息本身都进行持久化;
- 消费者开启确认机制并设置重试次数。
重复消费问题
重复消费的成因:消费者若在确认前宕机,重启后可能再次消费同一条消息。
解决方案:通过业务唯一标识(如订单号)检查数据库中数据是否已存在——若不存在则处理消息,若存在则忽略,避免重复消费。本质是幂等。
延迟队列
延迟队列 = 死信交换机 + TTL。
消息堆积
- 提高消费者消费能力,如使用多线程;
- 增加消费者数量,采用工作队列模式,让多个消费者并行消费同一队列;
- 扩大队列容量,使用 RabbitMQ 的惰性队列,支持数百万条消息存储,直接存盘而非内存。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!





















