服务依赖导致类无法自动注入的问题

本文详细描述了一个SpringBoot应用(cloud-auth)在依赖另一个服务(cloud-commons)时遇到的问题,即在调用cloud-commons中的工具类方法时出现空指针异常。原因是SpringBoot默认只扫描启动类同级及其子级包下的组件。解决方法包括在服务A中创建配置类或使用@ComponentScan注解手动扫描服务B的包。问题的根源在于Spring容器的扫描范围限制。

问题背景:服务A依赖了通用组件服务B,在A的pom.xml中依赖了服务B,但是当A服务启动的时候没有任何异常,调用B服务中的方法时会报空指针,排查到最后是B服务中提供的方法并没有被A服务容器纳入管理。
我的服务A是cloud-auth,其依赖了服务B:cloud-commons,pom文件如下:
在这里插入图片描述
调用工具类中的redisUtil工具类时报了空指针,因为RedisUtil并没有被cloud-auth的容器纳入管理,所以取不到:
在这里插入图片描述

解决方法:
1、在服务Acloud-auth中自定义一个配置类,然后new 一个RedisUtil被A的容器纳入管理,这个不建议用,服务B已经声明了一次还要在服务A中再声明,代码重合了。这里就不贴该方法的代码了
2、在服务Acloud-auth的启动类中使用@ComponentScan注解手动扫描服务B下的包,我服务A的文件都在com.cloud.cloudauth下,而服务B的文件都在com.cloud.cloudcommons下,因此可以使用@ComponentScan分别扫描这2个包,或者直接扫描com.cloud包。可以在服务A的启动类下加入如下注解即可。
在这里插入图片描述

产生问题的原因总结:springboot默认将启动类同级目录及子目录纳入容器的管理之中,而服务A的启动类在com.cloud.cloudauth包下,所以服务A的容器只会将该包下带有注解的类纳入容器管理,而服务B的都在com.cloud.cloudcommons下,自然就不会被纳入spring容器的管理了。

<think>在Java中,两个互相依赖注入(即循环依赖)在Spring框架中是一个常见的问题Spring通过三级缓存和提前暴露半成品bean的方式来解决单例作用域下的循环依赖问题。但是,对于原型作用域(prototype)的循环依赖Spring无法解决,会抛出BeanCurrentlyInCreationException异常。 ### 问题分析 假设有两个A和B,它们互相依赖: ```java @Component public class A { @Autowired private B b; } @Component public class B { @Autowired private A a; } ``` 在单例作用域下,Spring能够处理这种循环依赖Spring容器启动时,会先创建A的实例(此时A还未完全初始化,处于创建中的状态),然后发现A依赖B,于是开始创建B的实例。在创建B时,又发现B依赖A,但此时A已经在创建中(虽然还未完全初始化),Spring会将正在创建中的A的引用注入到B中,然后完成B的初始化,再将B注入到A中,最后完成A的初始化。 ### 解决方案 1. **使用构造器注入的循环依赖问题**:如果使用构造器注入,则Spring无法解决循环依赖,因为构造器注入需要依赖项在构造时就必须准备好。此时,Spring会抛出`BeanCurrentlyInCreationException`异常。因此,对于循环依赖的情况,建议使用字段注入(Field Injection)或Setter注入(Setter Injection)。 2. **避免循环依赖**:尽管Spring可以处理单例作用域下的循环依赖(通过Setter或字段注入),但循环依赖通常被视为设计上的问题。我们可以通过重新设计代码来避免循环依赖,例如引入第三个来包含公共逻辑,或者使用接口分离依赖。 3. **使用`@Lazy`注解**:在其中一个依赖上使用`@Lazy`注解,告诉Spring延迟初始化其中一个bean,从而打破循环依赖的初始化死锁。 ### 示例代码 #### 1. 使用字段注入(单例作用域下Spring自动解决) ```java @Component public class A { @Autowired private B b; } @Component public class B { @Autowired private A a; } ``` 这种方式在单例模式下,Spring能够自动解决循环依赖。 #### 2. 使用`@Lazy`注解 如果使用构造器注入,可以在其中一个构造器参数上使用`@Lazy`注解。 ```java @Component public class A { private B b; public A(@Lazy B b) { this.b = b; } } @Component public class B { private A a; @Autowired public B(A a) { this.a = a; } } ``` 这样,在创建A时,注入的是一个B的代理对象,而不是立即初始化B,从而打破循环。 ### 解释 - 在单例作用域下,Spring通过三级缓存(`singletonObjects`、`earlySingletonObjects`、`singletonFactories`)来解决循环依赖。具体过程是:在创建Bean时,首先将创建中的Bean放入三级缓存(`singletonFactories`),当需要注入依赖时,如果依赖的Bean正在创建中,则从三级缓存中获取一个早期引用(半成品),完成依赖注入,最后再完成初始化。 - 对于构造器注入的循环依赖,由于在构造时就需要完整的依赖,而依赖的Bean可能还未创建,因此无法解决。此时,必须使用`@Lazy`或改为Setter注入。 ### 建议 尽量避免循环依赖,因为它会导致代码耦合度增加,难以测试和维护。如果必须使用,确保使用单例作用域,并选择Setter注入或字段注入。 §§相关问题§§: 1. Spring框架如何处理单例作用域下的循环依赖? 2. 为什么构造器注入的循环依赖无法解决? 3. 如何避免循环依赖? 4. 在Spring中,除了单例作用域,还有哪些作用域?它们支持循环依赖吗?</think>在 Java 中,两个互相依赖注入(也称为循环依赖)**在 Spring 框架中是可能引发问题的**,但具体是否会导致错误取决于注入方式和 Spring 的配置。以下是详细分析和解决方案: --- ### 问题分析 1. **构造器注入的循环依赖**: 如果两个都使用构造器注入对方,**Spring 会直接抛出 `BeanCurrentlyInCreationException`**,因为无法确定先初始化哪个 Bean。 ```java @Service public class ServiceA { private final ServiceB serviceB; public ServiceA(ServiceB serviceB) { // 构造器注入 this.serviceB = serviceB; } } @Service public class ServiceB { private final ServiceA serviceA; public ServiceB(ServiceA serviceA) { // 构造器注入 this.serviceA = serviceA; } } ``` 2. **Setter/字段注入的循环依赖**: 如果使用 Setter 或字段注入(如 `@Autowired` 注解),**Spring自动解决单例作用域下的循环依赖**(通过三级缓存机制)。 ```java @Service public class ServiceA { @Autowired // 字段注入 private ServiceB serviceB; } @Service public class ServiceB { @Autowired // 字段注入 private ServiceA serviceA; } ``` --- ### 解决方案 #### 1. 避免构造器注入(推荐使用 Setter/字段注入) ```java @Service public class ServiceA { private ServiceB serviceB; @Autowired // Setter 注入 public void setServiceB(ServiceB serviceB) { this.serviceB = serviceB; } } @Service public class ServiceB { private ServiceA serviceA; @Autowired // Setter 注入 public void setServiceA(ServiceA serviceA) { this.serviceA = serviceA; } } ``` #### 2. 使用 `@Lazy` 延迟初始化 在其中一个依赖上添加 `@Lazy`,告诉 Spring 延迟初始化 Bean: ```java @Service public class ServiceA { private final ServiceB serviceB; public ServiceA(@Lazy ServiceB serviceB) { // 延迟初始化 this.serviceB = serviceB; } } ``` #### 3. 重构代码消除循环依赖(最佳实践) 通过引入中间接口或第三方解耦: ```java public interface CommonService { void commonMethod(); } @Service public class ServiceA implements CommonService { // 不再直接依赖 ServiceB } @Service public class ServiceB { @Autowired private CommonService commonService; // 依赖接口而非具体 } ``` #### 4. 使用 ApplicationContext 手动获取 Bean(不推荐) ```java @Service public class ServiceA { @Autowired private ApplicationContext context; public void useServiceB() { ServiceB serviceB = context.getBean(ServiceB.class); } } ``` --- ### Spring 如何解决循环依赖Spring 通过**三级缓存**解决单例 Bean 的循环依赖: 1. **一级缓存**:存放完全初始化好的 Bean 2. **二级缓存**:存放早期暴露的 Bean(未完全初始化) 3. **三级缓存**:存放 Bean 工厂(用于创建早期对象) 流程示例(A → B → A): 1. 创建 A 实例(未填充属性)→ 放入三级缓存 2. 填充 A 时发现依赖 B → 创建 B 实例 3. 填充 B 时发现依赖 A → 从三级缓存拿到 A 的早期引用 4. B 初始化完成 → A 获得 B 的引用 → A 初始化完成 --- ### 注意事项 1. **原型作用域(Prototype)**: 循环依赖在 `@Scope("prototype")` 下**无法解决**,会直接报错。 2. **构造器注入**: 始终避免在循环依赖中使用构造器注入。 3. **设计问题**: 循环依赖通常是代码设计不良的信号,建议通过重构解耦。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值