Bill L
Bill L

Reputation: 2826

Why does using @Autowired not lead to a circular dependency, but autowiring the constructor does?

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:

I'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

Answers (1)

Alexander Pavlov
Alexander Pavlov

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

Related Questions