Search code examples
javaspringspring-bootspring-annotationscross-reference

Spring boot cross-reference issue randomly occurs


I got a circular dependencies (cross-reference) issue when building a Spring boot project, and the dependencies trend like below:

  • Processor class autowired Criteria class via the constructor injection;
  • Criteria class autowired CacheManager via the constructor injection;
  • CahceManager class autowired the RuleSet class via the setter injection;
  • RuleSet class autowired the Processor again via the constructor injection.
The dependencies of some of the beans in the application context form a cycle:
   app
┌─────┐
|  XXXProcessor defined in file ...
↑     ↓
|  XXXCriteria defined in file ...
↑     ↓
|  XXXCacheManager
↑     ↓
|  XXXRuleSet defined in file ...
└─────┘

While I can make an effort to remove the dependency of Processor from RuleSet class, I was wondering if there is a way of keeping the current references but still eliminating the cross-reference issue as presented here? I looked up this forum and someone suggested that the @Lazy annotation might help. I tried to apply it to either the Processor class or the RuleSet class (on either class level or method level), the issue didn't go away.

Another observation is that, the above quoted error didn't appear all the time - sometime the program proceeds just fine, it's that the error randomly occurs that bugged me. And why is that?


Solution

  • One way to solve it is to replace one instance with a Provider like this:

    public Processor(Provider<Criteria> criteria) {
        this.criteria = criteria;
    }
    

    Then when using it you need to get() it first.

    Criteria c = this.criteria.get();
    

    This means that Processor can be constructed before Criteria since the injected Provider will get the Criteria bean once it's ready.

    This means that you cannot call get() in the constructor or you'll get a runtime error since that would still mean a circular construction dependency.

    @Lazy just means that Spring should wait with initialising a bean until it is actually requested instead of eagerly creating it on startup (which is standard behaviour). This has zero impact on both circular dependencies and beans that are injected into other beans constructors. It is useful for beans that are very slow to initialize and must almost always be used from a Provider to actually defer initialisation.