目录
    
   
    
     4.5 实现ApplicationContextAware and InitializingBean接口
    
   
    一、什么是循环依赖呢?
   
类A依赖类B,类B也依赖类A,这种情况就会出现循环依赖。
Bean A → Bean B → Bean A
上面是比较容易发现的循环依赖,也有更深层次的循环依赖。
Bean A → Bean B → Bean C → Bean D → Bean E → Bean A
    二、Spring 的循环依赖
   
当Spring上下文在加载所有的bean时,会尝试按照他们他们关联关系的顺序进行创建。如果不存在循环依赖时,例如:
Bean A → Bean B → Bean C
Spring会先创建Bean C,再创建Bean B(并将Bean C注入到Bean B中),最后再创建Bean A(并将Bean B注入到Bean A中)。
    但是,如果我们存在循环依赖,Spring上下文不知道应该先创建哪个Bean,因为它们依赖于彼此。在这种情况下,Spring会在加载上下文时,抛出一个
    
     
      BeanCurrentlyInCreationException
     
    
    。
   
当我们使用构造方法进行注入时,会遇到这种情况。因为它是上下文加载就被要求注入。
    
     三、举个栗子
    
   
用户类需要调用组织类中的方法,于是通过构造方法注入组织类。
@Service
public class UserService {
    
    private final DepartmentService departmentSerivce;
    /**
     * 通过构造方法注入DepartmentService类
     */
    @Autowired
    public UserSerivce(DepartmentService departmentService) {
        this.departmentService = departmentService;
    }
    public List<Department> list() {
        retern departmentService.list();
    }    
}而组织类也刚好需要调用用户类里的方法,于是它也通过构造方法注入用户类。
@Service
public class DepartmentService {
    
    private final UserService userSerivce;
    /**
     * 通过构造方法注入UserService类
     */
    @Autowired
    public DepartmentSerivce(UserService userSerivce) {
        this.userSerivce = userSerivce;
    }
    
}这种情况程序在编译时,就会报下面的错误
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
|  userService defined in file [D:\Java\IdeaProjects\UserService.class]
↑     ↓
|  departmentService defined in file [D:\Java\IdeaProjects\DepartmentService.class]
└─────┘
    现在我们可以为测试编写一个配置类,让我们称之为
    
     TestConfig
    
    ,它指定要扫描组件的基本包。让我们假设我们的bean在包”
    
     com.baeldung.circulardependency
    
    ”中定义:
   
@Configuration
@ComponentScan(basePackages = { "com.baeldung.circulardependency" })
public class TestConfig {
}最后,我们可以编写一个 JUnit 测试来检查循环依赖关系。测试可以为空,因为在上下文加载期间将检测到循环依赖关系。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class CircularDependencyTest {
    @Test
    public void givenCircularDependency_whenConstructorInjection_thenItFails() {
        // Empty test; we just want the context to load
    }
}如果您尝试运行此测试,您将得到以下异常:
BeanCurrentlyInCreationException: Error creating bean with name 'UserService':
Requested bean is currently in creation: Is there an unresolvable circular reference?
    四、解决办法
   
    4.1 重新设计
   
当出现这种循环依赖时,很可能是在设计方面存在问题,没有把每个类的职责很好的区分开。应该尝试正确重新设计组件,以便其层次结构设计良好,并且不需要循环依赖项。
如果无法重新设计,那么可以考虑其他解决办法。
    4.2 使用 setter/field 方法注入
   
上面说到,只有构造方法是在上下文加载时就要求被注入,容易出现依赖循环。所以可以用其他的方式进行依赖注入,setter 和 field 方法注入与构造方法不同,它们不会在创Bean时就注入依赖,而是在被需要时才注入。
    
     setter方法注入
    
   
@Service
public class UserService {
    
    private DepartmentService departmentSerivce;
   
    @Autowired
    public void setDepartmentSerivce(DepartmentService departmentService) {
        this.departmentService = departmentService;
    }
    public List<Department> list() {
        retern departmentService.list();
    }    
}另一个类也是同理使用setter方法注入
    
     field方法注入
    
   
    (使用
    
     
      @Autowired
     
    
    )
   
@Service
public class UserService {
    
    @Autowired
    private DepartmentService departmentSerivce;
    public List<Department> list() {
        retern departmentService.list();
    }    
}
    4.3 使用 @Lazy
   
    解决Spring 循环依赖的一个简单方法就是对一个Bean使用延时加载。也就是说:这个Bean并没有完全的初始化完,实际上他注入的是一个代理,只有当他首次被使用的时候才会被完全的初始化。
    
    我们对 UserService 进行修改,结果如下:
   
@Service
public class UserService {
    
    private DepartmentService departmentSerivce;
    @Autowired
    public UserSerivce(@Lazy DepartmentService departmentService) {
        this.departmentService = departmentService;
    }
    public List<Department> list() {
        retern departmentService.list();
    }    
}如果你现在运行测试,你会发现之前的错误不存在了。
    4.4 使用 @PostConstruct
   
打破循环的另一种方式是,在要注入的属性(该属性是一个bean)上使用 @Autowired,并使用@PostConstruct 标注在另一个方法,且该方法里设置对其他的依赖。
我们的Bean将修改成下面的代码:
@Service
public class UserService {
 
    @Autowired
    private DepartmentService departmentService;
 
    @PostConstruct
    public void init() {
        departmentService.setUserService(this);
    }
 
    public DepartmentService getDepartmentService() {
        return departmentService;
    }
}
@Service
public class DepartmentService {
 
    private UserService userService;
     
    private String message = "Hi!";
 
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
     
    public String getMessage() {
        return message;
    }
}
现在我们运行我们修改后的代码,发现并没有抛出异常,并且依赖正确注入进来。
    4.5 实现ApplicationContextAware and InitializingBean接口
   
如果一个Bean实现了ApplicationContextAware,该Bean可以访问Spring上下文,并可以从那里获取到其他的bean。实现InitializingBean接口,表明这个bean在所有的属性设置完后做一些后置处理操作(调用的顺序为init-method后调用);在这种情况下,我们需要手动设置依赖。
@Service
public class UserService implements ApplicationContextAware, InitializingBean {
 
    private DepartmentService departmentService;
 
    private ApplicationContext context;
 
    public DepartmentService getDepartmentService() {
        return departmentService;
    }
 
    @Override
    public void afterPropertiesSet() throws Exception {
        circB = context.getBean(DepartmentService.class);
    }
 
    @Override
    public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
        context = ctx;
    }
}
public class DepartmentService {
 
    private UserService userService;
 
    private String message = "Hi!";
 
    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
 
    public String getMessage() {
        return message;
    }
}
同样,我们可以运行之前的测试,看看有没有异常抛出,程序结果是否是我们所期望的那样。
    4.6 其他办法
   
我遇到的这个问题的情况,是 Service 之间互相依赖导致了深层次的循环依赖。但是我真正想调用的是 Mybatis Plus 提供的基础方法,所以可以将依赖 Service 换成依赖 Mapper,因为 Mapper 中也有这些基础方法。
@Service
public class UserService {
    
    private final DepartmentMapper departmentMapper;
    @Autowired
    public UserSerivce(DepartmentMapper departmentMapper) {
        this.departmentMapper = departmentMapper;
    }
    public List<Department> list() {
        retern departmentMapper.selectList();
    }    
}
    五、总结
   
在Spring中,有很多方法可以处理循环依赖关系。首先要考虑的是重新设计你的bean,这样就不需要循环依赖关系:它们通常是可以改进的设计的症状。但是,如果您绝对需要在项目中具有循环依赖项,则可以按照此处建议的一些解决方法进行操作。
    
     参考链接:
    
   
 
