【Java开发】Java面试七大专题第7篇:3. Spring bean 循环依赖【附代码文档】

🏆🏆🏆教程全知识点简介:基础篇 1. 二分查找 2. 冒泡排序 7. ArrayList 8. Iterator 9. LinkedList 10. HashMap 1)基本数据结构 2)树化与退化 3)索引计算 4)put 与扩容 5)并发问题 11. 单例模式 并发篇 1. 线程状态 3. wait vs sleep 4. lock vs synchronized 虚拟机篇 1. JVM 内存结构 4. 内存溢出 5. 类加载 6. 四种引用 7. finalize 框架篇 1. Spring refresh 流程 2. Spring bean 生命周期 6. Spring 注解 7. SpringBoot 自动配置原理 数据库篇 1. 隔离级别 2. 快照读与当前读 3. InnoDB vs MyISAM 4. 索引 索引基础 5. 查询语句执行流程 6. undo log 与 redo log 7. 锁 缓存篇 1. Redis 数据类型 2. keys 命令问题 3. 过期 key 的删除策略 5. 缓存问题 6. 缓存原子性 7. LRU Cache 实现 分布式篇 1. CAP 定理 2. Paxos 算法 4. Gossip 协议 5. 分布式通用设计 6. 一致性 Hash(补充)

📚📚仓库code.zip 👉直接-->: https://gitee.com/xiaoshuai112/Backend/blob/master/Java/Java面试七大专题/note.md 🍅🍅
✨ 本教程项目亮点
🧠 知识体系完整:覆盖从基础原理、核心方法到高阶应用的全流程内容
💻 全技术链覆盖:完整前后端技术栈,涵盖开发必备技能
🚀 从零到实战:适合 0 基础入门到提升,循序渐进掌握核心能力
📚 丰富文档与代码示例:涵盖多种场景,可运行、可复用
🛠 工作与学习双参考:不仅适合系统化学习,更可作为日常开发中的查阅手册
🧩 模块化知识结构:按知识点分章节,便于快速定位和复习
📈 长期可用的技术积累:不止一次学习,而是能伴随工作与项目长期参考
🎯🎯🎯全教程总章节
🚀🚀🚀本篇主要内容
3. Spring bean 循环依赖
要求
- 掌握单例 set 方式循环依赖的原理
- 掌握其它循环依赖的解决方法
循环依赖的产生
- 首先要明白,bean 的创建要遵循一定的步骤,必须是创建、注入、初始化三步,这些顺序不能乱
-
set 方法(包括成员变量)的循环依赖如图所示
-
可以在【a 创建】和【a set 注入 b】之间加入 b 的整个流程来解决
-
【b set 注入 a】 时可以成功,因为之前 a 的实例已经创建完毕
-
a 的顺序,及 b 的顺序都能得到保障
- 构造方法的循环依赖如图所示,显然无法用前面的方法解决
构造循环依赖的解决
- 思路1
- a 注入 b 的对象,这样能够保证 a 的流程走通
- 后续需要用到 b 的真实对象时,可以通过间接访问
- 思路2
- a 注入 b 的工厂对象,让 b 的实例创建被推迟,这样能够保证 a 的流程先走通
- 后续需要用到 b 的真实对象时,再通过 ObjectFactory 工厂间接访问
- 示例1:用 @Lazy 为构造方法参数生成
public class App60_1 {
static class A {
private static final Logger log = LoggerFactory.getLogger("A");
private B b;
public A(@Lazy B b) {
log.debug("A(B b) {}", b.getClass());
this.b = b;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
static class B {
private static final Logger log = LoggerFactory.getLogger("B");
private A a;
public B(A a) {
log.debug("B({})", a);
this.a = a;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("a", A.class);
context.registerBean("b", B.class);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
context.refresh();
System.out.println();
}
}
- 示例2:用 ObjectProvider 延迟依赖对象的创建
public class App60_2 {
static class A {
private static final Logger log = LoggerFactory.getLogger("A");
private ObjectProvider<B> b;
public A(ObjectProvider<B> b) {
log.debug("A({})", b);
this.b = b;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
static class B {
private static final Logger log = LoggerFactory.getLogger("B");
private A a;
public B(A a) {
log.debug("B({})", a);
this.a = a;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("a", A.class);
context.registerBean("b", B.class);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBe
## 4. Spring 事务失效
**要求**
* 掌握事务失效的八种场景
**1. 抛出检查异常导致事务不能正确回滚**
```java
@Service
public class Service1 {
@Autowired
private AccountMapper accountMapper;
@Transactional
public void transfer(int from, int to, int amount) throws FileNotFoundException {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
new FileInputStream("aaa");
accountMapper.update(to, amount);
}
}
}
-
原因:Spring 默认只会回滚非检查异常
-
解法:配置 rollbackFor 属性
@Transactional(rollbackFor = Exception.class)
2. 业务方法内自己 try-catch 异常导致事务不能正确回滚
[JDK 21 API 文档]
@Service
public class Service2 {
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) {
try {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
new FileInputStream("aaa");
accountMapper.update(to, amount);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
-
原因:事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉
-
解法1:异常原样抛出
-
在 catch 块添加
throw new RuntimeException(e);
-
解法2:手动设置 TransactionStatus.setRollbackOnly()
- 在 catch 块添加
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
3. aop 切面顺序导致导致事务不能正确回滚
@Service
public class Service3 {
@Autowired
private AccountMapper accountMapper;
[Testcontainers 文档]
@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) throws FileNotFoundException {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
new FileInputStream("aaa");
accountMapper.update(to, amount);
}
}
}
@Aspect
public class MyAspect {
@Around("execution(* transfer(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
LoggerUtils.get().debug("log:{}", pjp.getTarget());
try {
return pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
[JDK 8 API 文档]
return null;
}
}
[JDK 17 API 文档]
}
-
原因:事务切面优先级最低,但如果自定义的切面优先级和他一样,则还是自定义切面在内层,这时若自定义切面没有正确抛出异常…
-
解法1、2:同情况2 中的解法:1、2
- 解法3:调整切面顺序,在 MyAspect 上添加
@Order(Ordered.LOWEST_PRECEDENCE - 1)
(不推荐)
4. 非 public 方法导致的事务失效
@Service
public class Service4 {
@Autowired
private AccountMapper accountMapper;
@Transactional
void transfer(int from, int to, int amount) throws FileNotFoundException {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
accountMapper.update(to, amount);
}
}
}
-
原因:Spring 为方法创建、添加事务通知、前提条件都是该方法是 public 的
-
解法1:改为 public 方法
- 解法2:添加 bean 配置如下(不推荐) ```java @Bean
[Log4j 2 文档]
public Transact
- 点赞
- 收藏
- 关注作者
评论(0)