Spring Circular Dependencies
I know you've been there, like me:
- I Have a circular dependency in Spring and my application fails to launch. How to solve this?
The typical exception thrown by Spring is the following:
'org.springframework.security.authenticationManager': Requested bean is currently in creation: Is there an unresolvable circular reference?
And, It does not give much details. So, let's see how we can break the circular dependency cycle and save some sanity!
Understand the Issue¶
Let's take a very simple example to illustrate the issue. Here is part of our code handling the application permissions.
public interface PermissionByObject {
boolean accept(Object targetObject, String permission);
boolean test(User user, Object targetObject, String permission);
}
The SpringPermissionEvaluator
then requires all the PermissionByObject
implementations to be injected via collection autowiring.
final class SpringPermissionEvaluator implements SystemPermissionEvaluator {
List<PermissionByObject> byObject;
SpringPermissionEvaluator(final List<PermissionByObject> byObjects) {
super();
this.byObject = requireNonNull(byObjects);
}
@Override
public boolean hasPermission(
final Authentication authentication,
final Object targetObject,
final Object key) {
final User user = (User) authentication.getPrincipal();
final String permission = valueOf(key);
return byObject
.stream()
.filter(p -> p.accept(targetObject, permission))
.anyMatch(p -> p.test(user, targetObject, permission));
}
}
Suppose now that we have the following implementation named WorkspaceByObject
:
@Component
final class WorkspaceByObject implements PermissionByObject {
private final WorkspacePermission delegate;
WorkspaceByObject(WorkspacePermission delegate) {
this.delegate = checkNotNull(delegate);
}
@Override
public boolean accept(final Object targetObject, final String permission) {
return targetObject instanceof Workspace;
}
@Override
public boolean test(final User user, final Object targetObject, final String permission) {
// do something with the evaluator like delegating the call
}
}
We have a circular dependency here:
SystemPermissionEvaluator
requires allPermissionByObject
on construction,WorkspaceByObject
implementsPermissionByObject
and requiresSystemPermissionEvaluator
on construction.
We're going to explore the ways we can solve this issue.
Field Autowiring¶
The first and most obvious solution is to prevent the WorkspaceByObject
from requiring the SystemPermissionEvaluator
upon construction by using Field injection.
@Component
final class WorkspaceByObject implements PermissionByObject {
@Autowired
SystemPermissionEvaluator evaluator;
@Override
public boolean accept(final Object targetObject, final String permission) {
return targetObject instanceof Workspace;
}
@Override
public boolean test(final User user, final Object targetObject, final String permission) {
// do something with the evaluator like delegating the call
}
}
Setter Autowiring¶
Or Setter Injection:
@Component
final class WorkspaceByObject implements PermissionByObject {
SystemPermissionEvaluator evaluator;
@Override
public boolean accept(final Object targetObject, final String permission) {
return targetObject instanceof Workspace;
}
@Override
public boolean test(final User user, final Object targetObject, final String permission) {
// do something with the evaluator like delegating the call
}
public void setEvaluator(final SystemPermissionEvaluator evaluator) {
this.evaluator = evaluator;
}
}
Another solution would be to apply the same trick to the SystemPermissionEvaluator
. Why does it work? Because Spring can inject the dependencies after construction.
Using @Lazy¶
The @Lazy annotation allows Spring to inject a Java Proxy of your bean, instead of the bean itself. It can resolves the cycle by instantiating the beans when a method on it is called.
@Component
final class WorkspaceByObject implements PermissionByObject {
private final SystemPermissionEvaluator evaluator;
WorkspaceByObject(@Lazy final SystemPermissionEvaluator evaluator) {
super();
this.evaluator = checkNotNull(evaluator);
}
@Override
public boolean accept(final Object targetObject, final String permission) {
return targetObject instanceof Workspace;
}
@Override
public boolean test(final User user, final Object targetObject, final String permission) {
// do something with the evaluator like delegating the call
}
}
Because @Lazy
proxyfies your bean, calls to your service are done through reflection, thus being 2x slower than direct calls. Therefore, I would recommend using @Lazy
only when nothing else is possible.
Using @PostConstruct¶
PostConstruct can be used to annotate a method which is then called once the bean has been properly autowired. It can be effectively used to fix a circular dependency, although you should be aware it's pretty ugly to do so.
We can do so by altering the permission interface:
public interface PermissionByObject {
void setPermissionEvaluator(SystemPermissionEvaluator evaluator);
boolean accept(Object targetObject, String permission);
boolean test(User user, Object targetObject, String permission);
}
As you can see, this will pretty much affect every single PermissionByObject
implementation even if it's not using the SystemPermissionEvaluator
...
Then, we'll do this in the SystemPermissionEvaluator
:
final class SpringPermissionEvaluator implements SystemPermissionEvaluator {
List<PermissionByObject> byObject;
SpringPermissionEvaluator(final List<PermissionByObject> byObjects) {
super();
this.byObject = requireNonNull(byObjects);
}
@PostConstructor
void postConstruct() {
for (final PermissionByObject p : byObjects) {
p.setPermissionEvaluator(this);
}
}
}
Needless to say, this solution is exposing the internals of a specific permission to everyone. It's also affecting permissions which don't need the SystemPermissionEvaluator
being injected.
So, what can be do to solve this issue properly?
Using @Configuration¶
Another solution is to register the PermissionByObject
instances into the SystemPermissionEvaluator
using an external Spring Configuration within the same package. The key is to keep the registering logic invisible to external callers.
@Service
final class SpringPermissionEvaluator implements SystemPermissionEvaluator {
private final List<PermissionByObject> byObject = new ArrayList<>();
void register(final PermissionByObject p) {
byObject.add(p);
}
}
@Configuration
class PermissionConfig {
PermissionConfig(final SystemPermissionEvaluator evaluator, final List<PermissionByObject> permissions) {
super();
permissions.forEach(evaluator::register);
}
}
By doing this, the cycle is broken down. Both permission evaluator and permissions can be instantiated independently.
Changing the Code¶
A circular dependency issue should be an alarm screaming: Hey, there is something wrong with your code architecture! Of course, it's not always possible to fix the issue by yourself. It may happen due to a badly designed library you are depending on.
After figuring it out, in fact the WorkspaceByObject
has been rewritten (as well as other parts of the permission system) to completely avoid injecting the SystemPermissionEvaluator
at all!
Final Words¶
Always consider redesigning your code before deep diving into fancy solutions to fix spring circular dependencies issues. Use the methods described here as last resort when nothing else is possible.
Remember Spring isn't difficult! There is a solution to every problem related to Spring, and chances are that you are not the single one experiencing those issues. Always look on Spring StackOverflow to see if someone else has had the same issue.