This is an example from the Spring documentation, section 6.12.5:
@Configuration
public class ServiceConfig {
@Autowired
private AccountRepository accountRepository;
@Bean
public TransferService transferService() {
return new TransferServiceImpl(accountRepository);
}
}
My question is: why must it happen that accountRepository
is created before it's used by new TransferServiceImpl()
? Offhand, I don't see how Spring could know that the second one depends on the first one being set up (unless it goes through the transferService()
bytecode). Is it because something about the order in which Spring does things guarantees that the @Autowired
variable is processed before the @Bean
method could possibly be called? What is the processing order? What kinds of circumstances could cause Spring to process these out of order?
The reason I'm asking is that I have a case where something like this isn't working, i.e. the new
is being executed with a null
argument. Either the @Autowired
variable is being set up too late, or it isn't set up at all (my guess is the latter, based on some log4j.logger.org.springframework.beans
debugging output, but I'm not sure). The situation is of course much more complex--it's a largish application, and there are a few more @Autowired
and @Bean
definitions in the configuration class. Using @DependsOn
hasn't helped. It will take a lot of time to narrow down the problem by deleting code until I can get a minimal example, but I wanted to see if I could get some insight into the problem by learning more details about how Spring processes things, before starting down the difficult code reduction path.
why must it happen that
accountRepository
is created before it's used by newTransferServiceImpl()
?
It doesn't. accountRepository
may be seen to be null
.
From the note in the documentation you linked (its more current version)
Make sure that the dependencies you inject that way are of the simplest kind only.
@Configuration
classes are processed quite early during the initialization of the context and forcing a dependency to be injected this way may lead to unexpected early initialization. Whenever possible, resort to parameter-based injection as in the example above.Also, be particularly careful with
BeanPostProcessor
andBeanFactoryPostProcessor
definitions via@Bean
. Those should usually be declared asstatic
@Bean
methods, not triggering the instantiation of their containing configuration class. Otherwise,@Autowired
and@Value
won’t work on the configuration class itself since it is being created as a bean instance too early.
In summary, a Configuration
class will end up being just another bean in the application context. As such, it will be processed by all registered BeanPostProcessor
beans.
@Autowired
is processed by AutowiredAnnotationBeanPostProcessor
. Presumably, you're using AnnotationConfigApplicationContext
which registers one automatically.
In your example, which is incomplete since
...but determining exactly where the autowired bean definitions are declared is still somewhat ambiguous
However, we can assume some other configuration provided a bean definition for a AccountRepository
bean. Once the application context instantiates the ServiceConfig
bean, it can then post process it and inject @Autowired
targets.
The only reason an @Autowired
target could be null
in a @Configuration
bean instance is that you tried to read it before an AutowiredAnnotationBeanPostProcessor
could process/inject it.
Consider a circular dependency. Take the @Configuration
class in your snippet with an additional @ComponentScan
of the following classes
@Component
class AccountRepository {
public AccountRepository(Foo foo) {}
}
@Component
class Foo {
public Foo(TransferService ts) {}
}
The @Configuration
bean get initialized. AutowiredAnnotationBeanPostProcessor
kicks off to process the accountRepository
field. It looks for an AccountRepository
bean and tries to initialize it. It needs a Foo
bean to instantiate it (for constructor injection). It looks for a Foo
bean and tries to initialize it. It needs a TransferService
bean to instantiate it (for constructor injection). It looks for a TransferService
bean and finds the @Bean
factory method. It invokes it. The accountRepository
hasn't been initialized yet, so remains null
. You can verify this by putting a breakpoint in the @Bean
method and browsing the stack trace.
Had you used a parameter injection as suggested in the quote above
Whenever possible, resort to parameter-based injection as in the example above.
Spring would've crashed and warned you
Caused by:
org.springframework.beans.factory.BeanCurrentlyInCreationException
: Error creating bean with name'accountRepository'
: Requested bean is currently in creation: Is there an unresolvable circular reference?
That's the workaround I ended up doing
I can't currently explain this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With