Learnitweb

Circular dependency in Spring with example

Overview

In this tutorial, we’ll discuss circular dependency in Spring with example. Circular dependency generate exception while creating beans. We’ll create a circular dependency and will see how to fix this.

Circular dependency in Spring

Before looking at circular dependency, we need to understand how beans are created by container. Container is intelligent enough to resolve dependencies and create the beans. Bean properties are not set until the bean is actually created.

Circular dependency issue may arise if we use constructor dependency injection. Let us understand this with the help of an example.

Suppose ClassA has a dependency on ClassB and ClassB has a dependency on ClassA. If we use constructor dependency injection, container will try to create ClassA but will notice that it has dependency on ClassB. So container will try to create ClassB before creating ClassA. While creating ClassB, container will notice that it has dependency on ClassA. Container can not create either of the two as both are dependent on each other. We’ll get BeanCurrentlyInCreationException when circular dependency is encountered.

ClassA -> ClassB -> ClassA

Circular dependency may occur in case of multiple classes as well.

ClassA -> ClassB -> ClassC -> ClassA

Let us see this with an example. In this example, we’ll use Spring Boot application to test. The reason is Spring Boot applications are very popular and the common way of creating application. By doing so we’ll use annotations to write our code.

package com.learnitweb.dependency;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ClassA {
	@Autowired
	private ClassB classB;

	public ClassA(ClassB classB) {
		super();
		this.classB = classB;
	}

}
package com.learnitweb.dependency;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ClassB {

	@Autowired
	private ClassA classA;

	public ClassB(ClassA classA) {
		super();
		this.classA = classA;
	}
}

Let us now test our application. We’ll get BeanCurrentlyInCreationException exception.

package com.learnitweb.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages = { "com.learnitweb.dependency" })
public class DemoApplication {

	public static void main(String[] args) {
		try {
			SpringApplication.run(DemoApplication.class, args);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

Workarounds for circular dependency

1. Redesign your dependencies

One solution is to redesign your dependencies so that circular dependency not occur at first place. This is the best solution as circular dependency can be avoided. However, if it is not possible to redesign the dependencies, there are workarounds to solve circular dependency issues.

2. Use Setter/Field dependency injection

One possible is to use setter dependency injection instead of constructor dependency injection. So we’ll remove the constructor based on fields and use setter method instead.

public class ClassA {
	ClassB classB;

	public void setClassB(ClassB classB) {
		this.classB = classB;
	}
}
package com.learnitweb.dependency;

public class ClassB {
	ClassA classA;

	public void setClassA(ClassA classA) {
		this.classA = classA;
	}
}

3. Lazy initialization of bean using @Lazy

We can lazy initialize one of the bean and can break the cycle. We can use @Lazy annotation to declare that the bean the lazily initialized. This will create the proxy and will inject. The injected bean is fully created only when it is required.

package com.learnitweb.dependency;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

@Component
public class ClassA {
	@Autowired
	private ClassB classB;

	public ClassA(@Lazy ClassB classB) {
		super();
		this.classB = classB;
	}

}

4. Use @PostConstruct

Another way to break the cycle is to set the dependency using @PostConstruct in one of the bean. In our case, we’ll do the changes in ClassA and will set dependency of ClassB in method annotated with @PostConstruct.

package com.learnitweb.dependency;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ClassA {
	@Autowired
	private ClassB classB;

	@PostConstruct
	public void init() {
		classB.setClassA(this);
	}

	public ClassB getClassB() {
		return classB;
	}

	public void setClassB(ClassB classB) {
		this.classB = classB;
	}
}
import org.springframework.stereotype.Component;

@Component
public class ClassB {

	private ClassA classA;

	public ClassA getClassA() {
		return classA;
	}

	public void setClassA(ClassA classA) {
		this.classA = classA;
	}
}

5. Implementing ApplicationContextAware and InitializingBean interfaces

This is another way to break the cycle of dependencies. Implementing ApplicationContextAware interface enables us to access context from which we can get the bean. Implementing InitializingBean enables us to do something once all their properties have been set by a BeanFactory. We can do custom initialization by implementing this interface and we’ll initialize our dependency.

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class ClassA implements ApplicationContextAware, InitializingBean {
	private ClassB classB;

	private ApplicationContext context;

	public ClassB getClassB() {
		return classB;
	}

	public ApplicationContext getContext() {
		return context;
	}

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

	@Override
	public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
		context = ctx;
	}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ClassB {

	private ClassA classA;

	@Autowired
	public void setCircA(ClassA classA) {
		this.classA = classA;
	}

}

Conclusion

We saw that circular dependency issue may arise if constructor dependency injection is used. There are workarounds to handle this issue. But the recommended way is to design the dependencies in such a manner that workarounds should not be required. But if it is not possible to redesign the dependencies for any reason, we may use the workarounds. This is a very common interview question in Spring and every developer should be aware about the issue and workarounds.