Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is an @Autowired field within a @Configuration class null?

Tags:

java

spring

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.

like image 697
ajb Avatar asked Dec 12 '16 23:12

ajb


1 Answers

why must it happen that accountRepository is created before it's used by new TransferServiceImpl()?

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 and BeanFactoryPostProcessor definitions via @Bean. Those should usually be declared as static @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.

like image 148
Sotirios Delimanolis Avatar answered Oct 14 '22 08:10

Sotirios Delimanolis