How to Handle Cyclic Dependency Between Beans in Spring?

Anh Trần Tuấn - Sep 15 - - Dev Community

1. What is a Cyclic Dependency?

Image

A cyclic dependency occurs when two or more beans depend on each other, creating a cycle. For example, Bean A depends on Bean B, and Bean B depends on Bean A. This circular reference can lead to issues during the initialization of beans in a Spring application.

1.1 Understanding the Problem

In Spring, when a bean is being created, its dependencies are injected. If these dependencies form a cycle, Spring will enter an infinite loop trying to resolve the beans, eventually leading to an error.

1.2 Example of a Cyclic Dependency

Consider the following example:

@Component
public class BeanA {
    private final BeanB beanB;

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

@Component
public class BeanB {
    private final BeanA beanA;

    @Autowired
    public BeanB(BeanA beanA) {
        this.beanA = beanA;
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, BeanA depends on BeanB , and BeanB depends on BeanA. This creates a cyclic dependency.

1.3 Common Errors Due to Cyclic Dependencies

When Spring encounters a cyclic dependency, you may see errors such as BeanCurrentlyInCreationException. This indicates that Spring is trying to create a bean that depends on another bean that is not yet fully initialized.

1.4 Identifying Cyclic Dependencies

Spring's error messages often point directly to the beans involved in the cycle, making it easier to identify the problematic code. However, in complex applications, it may be necessary to review the bean configurations and dependencies manually.

2. How to Resolve Cyclic Dependencies

Several approaches can be used to resolve cyclic dependencies in Spring, including the use of @lazy , constructor injection, and refactoring the code.

2.1 Using @lazy Annotation

One of the simplest ways to resolve a cyclic dependency is by using the @lazy annotation. This annotation tells Spring to initialize the bean lazily, i.e., only when it's actually needed.

@Component 
public class BeanA { 

    private final BeanB beanB; 

    @Autowired 
    public BeanA(@Lazy BeanB beanB) { 
        this.beanB = beanB; 
    } 
}
Enter fullscreen mode Exit fullscreen mode

By applying @lazy to BeanB , Spring will not attempt to inject BeanB into BeanA until BeanB is actually needed. This breaks the cycle and allows both beans to be created successfully.

2.2 Refactoring to Remove Cyclic Dependencies

Another approach is to refactor the code to eliminate the cyclic dependency. This may involve introducing a third bean to handle the common functionality or rethinking the design to remove the direct dependency between the two beans.

Example: Introducing a Third Bean

@Component
public class BeanA {
    private final CommonService commonService;

    @Autowired
    public BeanA(CommonService commonService) {
        this.commonService = commonService;
    }
}

@Component
public class BeanB {
    private final CommonService commonService;

    @Autowired
    public BeanB(CommonService commonService) {
        this.commonService = commonService;
    }
}

@Component
public class CommonService {
    // Common logic used by both BeanA and BeanB
}
Enter fullscreen mode Exit fullscreen mode

In this example, a new CommonService bean is introduced to handle the common functionality. Both BeanA and BeanB depend on CommonService , but there is no longer a direct dependency between BeanA and BeanB.

2.3 Constructor Injection vs. Field Injection

Constructor injection is generally preferred over field injection because it makes dependencies explicit and easier to manage. However, cyclic dependencies are more likely to occur with constructor injection. If you encounter a cyclic dependency, consider using @lazy or refactoring as shown above.

2.4 Demo: Resolving a Cyclic Dependency

Let's see how these techniques work in practice. Here's a simple Spring Boot application demonstrating the resolution of a cyclic dependency using @lazy.

Step 1: Setup the Application

Create a new Spring Boot project with the following dependencies:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
</dependencies>
Enter fullscreen mode Exit fullscreen mode

Step 2: Implement the Beans

@SpringBootApplication
public class CyclicDependencyDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(CyclicDependencyDemoApplication.class, args);
    }
}

@Component
public class BeanA {
    private final BeanB beanB;

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

@Component
public class BeanB {
    private final BeanA beanA;

    @Autowired
    public BeanB(BeanA beanA) {
        this.beanA = beanA;
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Run the Application

When you run the application, Spring will successfully initialize both BeanA and BeanB without encountering a cyclic dependency error, thanks to the @lazy annotation.

3. Conclusion

Handling cyclic dependencies between beans in Spring can be straightforward with the right approach. Whether using @lazy , refactoring the code, or introducing a common service, it's crucial to understand the nature of the cyclic dependency and choose the appropriate solution.

If you have any questions or need further clarification, feel free to comment below!

Read posts more at : How to Handle Cyclic Dependency Between Beans in Spring?

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .