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.