Youssef Ameachaq's Blog

Youssef Ameachaq

Circular Dependencies in Spring


What Is a Circular Dependency?

Sometimes in programming, things need each other to work properly. But when two things depend on each other and can’t start without the other, that’s called a circular dependency. For example, if ‘bean A’ needs ‘bean B’ to work, but ‘bean B’ also needs ‘bean A’ to start, then we have a circular dependency. This can also happen with more beans involved, like if ‘bean A’ needs ‘bean B’, which needs ‘bean C’, and so on, until eventually ‘bean E’ needs ‘bean A’ again.

Circular Dependency in Spring

When Spring loads all the things it needs to run, it puts them in order so they’ll work together properly. If we have something like ‘bean A’ needing ‘bean B’, and ‘bean B’ needing ‘bean C’, Spring will create ‘bean C’ first, then ‘bean B’ with ‘bean C’ inside it, and finally ‘bean A’ with ‘bean B’ inside it.

But if we have a circular dependency, where ‘bean A’ needs ‘bean B’, and ‘bean B’ needs ‘bean A’ back, Spring can’t decide which bean to make first. This can cause an error while loading things up.

It’s more likely to happen if we use constructor injection, but if we use other ways to connect things, we shouldn’t have this problem. That’s because Spring will wait to put things together until it actually needs them, instead of trying to make them all at once.

Example

Imagine we have two things that need each other to work. We’ll call them ‘bean 1’ and ‘bean 2’. They rely on each other using something called constructor injection.

@Component
public class BeanA {

    private BeanB circB;

    @Autowired
    public BeanA(BeanB circB) {
        this.circB = circB;
    }
}
@Component
public class BeanB {

    private BeanA circA;

    @Autowired
    public BeanB(BeanA circA) {
        this.circA = circA;
    }
}

To test these two things, we need to set up a special class that knows where to find them. We’ll call this class ‘TestConfig’. It tells the program to look in a specific folder called ‘com.youssefameachaq.circulardependency’ to find our two things, which we call ‘beans’.

@Configuration
@ComponentScan(basePackages = { "com.youssefameachaq.circulardependency" })
public class TestConfig {
}

Once we have the two things set up and we know where to find them, we need to make sure they work together properly. We can use a special tool called ‘JUnit’ to test them.

To check the circular dependency, we can make an empty test. This will actually help us because when we try to run the test, the program will automatically check to see if there’s a problem with the way the two things are working together. If there is a problem, we’ll know because the test will fail.

@ContextConfiguration(classes = { TestConfig.class })
public class BeanIntegrationTest {

    @Test
    public void givenBean_whenConstructorInjection_thenItFails() {
        // It's an empty test, we just want the context to load
    }
}

When we run the test, we will get this exception:

BeanCurrentlyInCreationException: Error creating bean with name 'beanA':
Requested bean is currently in creation: Is there an unresolvable circular reference?

Solutions

Next, we will present some of the most commonly used approaches to address this issue.

Think about your design

Circular dependencies in code may suggest a design issue, and the responsibilities of components may not be properly separated. Ideally, we should redesign the components to remove the need for circular dependencies.

However, redesigning may not always be possible due to various reasons, such as legacy code, code that cannot be modified due to testing, time or resource constraints, etc. In such cases, we can try to use workarounds to deal with circular dependencies.

Use @Lazy annotation

To fix circular dependency issues, we can use lazy initialization in Spring. This means that instead of fully initializing a bean, Spring creates a proxy to inject it into another bean. The injected bean will only be fully created when it’s first needed.

To implement lazy initialization, we can modify the BeanA class like this:

@Component
public class BeanA {
    private BeanB circB;

    @Autowired
    public BeanA(@Lazy BeanB circB) {
        this.circB = circB;
    }
}

This change will allow the Spring context to load without raising a circular dependency exception.

Use Setter Injection

We can use setter injection or field injection instead of constructor injection. This way, Spring creates the beans, but the dependencies are not injected until they are needed.

To demonstrate this, let’s modify our classes to use setter injection and add a new field called “message” to BeanB so that we can write a proper unit test:

@Component
public class BeanA {

    private BeanB circB;

    @Autowired
    public void setCircB(BeanB circB) {
        this.circB = circB;
    }

    public BeanB getCircB() {
        return circB;
    }
}
@Component
public class BeanB {

    private BeanA circA;

    private String message = "Hi!";

    @Autowired
    public void setCircA(BeanA circA) {
        this.circA = circA;
    }

    public String getMessage() {
        return message;
    }
}

We need to update our unit test to reflect the changes we made in the code.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class BeanIntegrationTest {

    @Autowired
    ApplicationContext context;

    @Bean
    public BeanA getBeanA() {
        return new BeanA();
    }

    @Bean
    public BeanB getBeanB() {
        return new BeanB();
    }

    @Test
    public void givenBean_whenSetterInjection_thenItWorks() {
        BeanA circA = context.getBean(BeanA.class);

        Assert.assertEquals("Hi!", circA.getCircB().getMessage());
    }
}

Use @PostConstruct

Another way to solve the problem is by using a combination of constructor injection and @PostConstruct method. We can inject one of the dependencies using the @Autowired annotation and then set the other dependency using a method annotated with @PostConstruct.

Here’s an example implementation:

@Component
public class BeanA {

    @Autowired
    private BeanB circB;

    @PostConstruct
    public void init() {
        circB.setCircA(this);
    }

    public BeanB getCircB() {
        return circB;
    }
}
@Component
public class BeanB {

    private BeanA circA;
	
    private String message = "Hi!";

    public void setCircA(BeanA circA) {
        this.circA = circA;
    }
	
    public String getMessage() {
        return message;
    }
}

Implement ApplicationContextAware and InitializingBean

If we make one of the beans implement the ApplicationContextAware interface, it will have access to the Spring context and can retrieve the other bean from there.

In addition, by implementing the InitializingBean interface, we can specify that this bean needs to perform certain actions after all its properties have been set. We can use this to manually set our dependency.

Here’s an example code for our beans:

@Component
public class BeanA implements ApplicationContextAware, InitializingBean {

    private BeanB circB;

    private ApplicationContext context;

    public BeanB getCircB() {
        return circB;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        circB = context.getBean(BeanB.class);
    }

    @Override
    public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
        context = ctx;
    }
}
@Component
public class BeanB {

    private BeanA circA;

    private String message = "Hi!";

    @Autowired
    public void setCircA(BeanA circA) {
        this.circA = circA;
    }

    public String getMessage() {
        return message;
    }
}

In Spring, there are several ways to handle circular dependencies. One of the best approaches is to redesign the beans so that circular dependencies are no longer needed. Circular dependencies typically indicate that the design can be improved.

However, if circular dependencies are unavoidable, there are various workarounds that we can use. The most recommended method is using setter injections. Nevertheless, there are other options available, which usually involve preventing Spring from managing the initialization and injection of the beans and instead accomplishing this ourselves using various techniques.

Github project


Main Source : baeldung.com/circular-dependencies-in-spring