Reputation: 2826
I've noticed a bit of a weird bug in an application I'm working on. While modifying a class, I moved a bunch of properties to be autowired in the constructor instead of using field injection, however, this caused me to get an error while starting up because of a circular dependency. Here's the breakdown of the dependency that caused an error:
ServiceA
inside of TargetClass
.ServiceA
has ServiceB
injected through its constructorServiceB
has ServiceC
injected through field injectionServiceC
has TargetClass
injected through field injectionI'm looking into ways of refactoring this, or trying to move some logic around into better places so I remove this circular dependency, however, I'm curious as to why two different methods of injecting ServiceA
cause two different outcomes:
If I inject ServiceA
through field injection, just using @Autowired
on the field, my app starts just fine. However, if I switch TargetClass
to use constructor injection, I will get an error about a circular dependency.
What is the difference in the way Spring handles the two types of injection that causes one to fail in this scenario, and the other to work?
Upvotes: 3
Views: 1675
Reputation: 2210
Field injection works like
ServiceA serviceA = new ServiceA();
ServiceB serviceB = new ServiceB();
serviceA.serviceB = serviceB;
serviceB.serviceA = serviceA;
Constructor injection fails because
ServiceA serviceA = new ServiceA(????); // Cannot inject serviceB because to create serviceB I need serviceA which is being constructed
Though, there is way to have circular deps even with constructor.
class ServiceA {
javax.inject.Provider<ServiceB> serviceBProvider;
@Autowired ServiceA(javax.inject.Provider<ServiceB> serviceBProvider) {
this.serviceBProvider = serviceBProvider;
}
void later() {
this.serviceBProvider.get().methodOfServiceB();
}
void methodOfServiceA() {}
}
class ServiceB {
javax.inject.Provider<ServiceA> serviceAProvider;
@Autowired ServiceB(javax.inject.Provider<ServiceA> serviceAProvider) {
this.serviceAProvider = serviceAProvider;
}
void later() {
this.serviceAProvider.get().methodOfServiceA();
}
void methodOfServiceB() {}
}
It works because Spring does smth like that
Provider<ServiceA> serviceAProvider = new Provider<>();
Provider<ServiceB> serviceBProvider = new Provider<>();
ServiceA serviceA = new ServiceA(serviceBProvider);
ServiceB serviceB = new ServiceB(serviceAProvider);
serviceAProvider.set(serviceA);
serviceBProvider.set(serviceB);
Upvotes: 3