likes
comments
collection
share

Springboot又循环依赖了?!介绍6种解决方案

作者站长头像
站长
· 阅读数 27

前言

大家好,我是大鱼七成饱。这敲代码敲的手快秃噜皮,一不小心就环引用了,恰好旁边同事也遇到,为解决这个问题,稍微花了点时间。俗话说好记性不如烂笔头,这次整理下循环引用的各种解决方案,下次谁问就假装大神,拍下脑门就出答案。

Springboot又循环依赖了?!介绍6种解决方案

一、啥是循环依赖

简单来说就是A依赖B,B也依赖A。也可能是A依赖C,C依赖B,B依赖A。对于Java或者go等开发者来说,这并不是个新问题,面试也经常问到。Spring框架通过一个称为"三级缓存"的机制来处理循环依赖,以此来保证Spring容器可以正确地解析和初始化所有的beans。

二、为什么么Springboot要禁止循环依赖

先说正常的依赖,比如上面的A依赖B,B依赖C。这个时候spring可以分析出依赖关系,判断出要先创建C,然后创建B,将C注入B,最后创建A注入。

当存在循环依赖的时候,且是构造器注入的时候,Spring不好判断先创建哪个,从而报BeanCurrentlyInCreationException异常。因此,从2.6版本开始,如果你的项目里还存在循环依赖,SpringBoot将拒绝启动。

Springboot又循环依赖了?!介绍6种解决方案

三、六种解决方案

三个Service的初始代码如下(A->B->C->A):

@Service
public class ServiceA {
    @Resource
    private ServiceC serviceC;
}
@Service
public class ServiceB {
    @Resource
    private ServiceC serviceC;
}
@Service
public class ServiceC {
    @Resource
    private ServiceA serviceA;
}
1、添加允许循环引用配置
spring.main.allow-circular-references = true

缺点:不支持构造方法注入

@Service
public class ServiceA {
    private ServiceB serviceB;
    @Autowired
    public ServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}


@Service
public class ServiceB {

    private ServiceC serviceC;

    @Autowired
    public ServiceC(ServiceA serviceC) {
        this.serviceC = serviceC;
    }
}

@Service
public class ServiceC {

    @Resource
    private ServiceA serviceA;

    @Autowired
    public ServiceA(ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}

Springboot又循环依赖了?!介绍6种解决方案

2、添加@Lazy注解

构造方法注入或者Set注入的将其中一个添加@Lazy,注解,这样有一个延迟注入,从而打破了循环引用的环。

@Service
public class ServiceC {
    private ServiceA serviceA;
//    @Autowired
//    public ServiceC(@Lazy ServiceB serviceB) {
//        this.serviceB = serviceB;
//    }
    @Autowired
    public void setServiceA(@Lazy ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}
3、添加@PostContruct注解延迟注入

这种方法更方便两个类互相依赖的处理,A和B互相依赖。A延迟到PostConstruct注入

@Service
public class ServiceA {
    private ServiceB serviceB;
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}
@Service
public class ServiceB {
    @Resource
    private ServiceA serviceA;
    @PostConstruct
    public void init() {
        serviceA.setServiceB(this);
    }
}
4、afterPropertiesSet之后注入

这个方法需要继承ApplicationContextAware和InitializingBean接口。属于另一种延后注入的方法。

@Service
public class ServiceB implements ApplicationContextAware, InitializingBean {
    private ServiceA serviceA;
    private ApplicationContext applicationContext;
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    
    @Override
    public void afterPropertiesSet() throws Exception {
        serviceA = applicationContext.getBean(ServiceA.class)
    }
}
5、使用时获取

这个方法使用了ApplicationContext,实际调用方法时直接获取该service.

@Service
public class ServiceA {
    @Resource
    private ServiceB serviceB;
    public void sayHello() {
        System.out.println("hi");
    }
}

@Service
public class ServiceB  {
    private ServiceA serviceA;

    @Resource
    private ApplicationContext applicationContext;

    public void helloWorld() {
        ServiceA bean = applicationContext.getBean(ServiceA.class);
        bean.sayHello();
    }
}
6、使用消息机制解耦引用关系

相互依赖的原因大部分是需要互相调用方法,那么可以用消息的解耦特性,打破互相调用。

以下是示例:

@Service
public class ServiceA {

    @EventListener
    public void sayHello(HiEvent event) {
        System.out.println("hi");
    }
}
@Service
public class ServiceB  {

    @Resource
    private ApplicationContext applicationContext;

    public void helloWorld() {
        applicationContext.publishEvent(new HiEvent());
    }
}

四、总结

本文梳理了解决循环依赖的几种方法。大体分三类:配置解决,延后注入使用和解耦调用关系。如果出现了循环依赖,大体是设计有点问题,是软件开发中的坏味道,如果暂时无法从架构层面解决,那可以根据场景选择适合的方案。

Springboot又循环依赖了?!介绍6种解决方案

转载自:https://juejin.cn/post/7388328892309504034
评论
请登录