Keywords: Spring Security | Circular Dependency | Setter Injection
Abstract: This article explores the common circular dependency problems in Spring Security applications, particularly when using JdbcTemplate for database queries. Through a detailed case study of a Vaadin Spring application, it explains the formation mechanism of circular dependencies and proposes solutions based on the best answer, focusing on Setter injection. Additionally, it supplements with other methods like @Lazy annotation and Bean definition refactoring, providing comprehensive technical guidance. The content covers Spring Boot version differences, dependency injection pattern comparisons, and practical code examples to help developers understand and resolve similar issues.
Introduction
Circular dependency is a common yet challenging issue in Spring Security application development. This article uses a Vaadin application with Spring Boot 1.4.2.RELEASE as an example to analyze the circular dependency that arises when querying databases via jdbcTemplate. This problem does not occur in Spring Boot 1.1.x versions but causes application startup failure in newer versions, with error messages clearly indicating the dependency cycle: jdbcAccountRepository → WebSecurityConfig → jdbcUserDetailsServices → jdbcAccountRepository. Such circular dependencies not only affect startup but may also lead to runtime exceptions, necessitating prompt resolution.
Formation Mechanism of Circular Dependencies
Circular dependencies typically occur during Spring IoC container initialization when two or more Beans depend on each other, forming a closed loop that prevents the container from determining the initialization order. In this case, the dependency chain is as follows: JdbcAccountRepository depends on PasswordEncoder (via constructor injection), PasswordEncoder is defined in WebSecurityConfig, WebSecurityConfig depends on JdbcUserDetailsServices, and finally JdbcUserDetailsServices depends on JdbcAccountRepository. This structure triggers circular dependency detection in Spring Boot 1.4.2.RELEASE, preventing application startup.
Solution: Using Setter Injection
According to the Spring official documentation, circular dependencies are difficult to resolve with constructor injection because the container needs to inject dependencies before Beans are fully initialized. The best solution is to switch from constructor injection to Setter injection. Setter injection allows the container to create Bean instances first and then inject dependencies via Setter methods, thereby breaking the cycle. For example, modify the code for JdbcAccountRepository:
@Repository
public class JdbcAccountRepository implements AccountRepository {
private JdbcTemplate jdbcTemplate;
private PasswordEncoder passwordEncoder;
@Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Autowired
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
// Other methods remain unchanged
}Similarly, adjust the dependency injection in JdbcUserDetailsServices and WebSecurityConfig. This method is simple and effective, but note that Setter injection may reduce code immutability; use it only when necessary.
Additional Supplementary Solutions
Besides Setter injection, other methods can resolve circular dependencies:
- Use the
@Lazyannotation: Add@Lazyto a Bean in the dependency chain to delay its initialization and break the cycle. For example, add@Lazyto the@Autowiredfield inJdbcUserDetailsServices. This method maintains constructor injection but may increase runtime complexity. - Refactor Bean definitions: Move the
PasswordEncoderBean definition out ofWebSecurityConfig, e.g., to theSecurityConfigurationclass. This eliminates the indirect dependency ofWebSecurityConfigonJdbcUserDetailsServices, resolving the cycle.
Code Examples and Best Practices
To clearly demonstrate the solution, here is an integrated code snippet using Setter injection:
// WebSecurityConfig in SecurityConfiguration class
@Configuration
@EnableWebSecurity
public static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private JdbcUserDetailsServices userDetailsService;
@Autowired
public void setUserDetailsService(JdbcUserDetailsServices userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
// Other configurations remain unchanged
}In practice, prioritize constructor injection for better testability and immutability, and consider Setter injection or @Lazy annotation only when circular dependencies arise. Additionally, regularly update Spring Boot versions, as newer releases may optimize dependency resolution mechanisms.
Conclusion
Circular dependencies are a common issue in Spring applications, especially when integrating Spring Security with database queries. Through a detailed case study, this article demonstrates the effectiveness of using Setter injection as the primary solution and supplements it with other methods like @Lazy annotation and Bean definition refactoring. Developers should choose appropriate solutions based on project needs to ensure stable application operation. In the future, as the Spring ecosystem evolves, dependency management tools may offer more elegant solutions.