Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Javaconfig inter-bean dependencies

Browsing the Spring Javaconfig reference documentation http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/beans.html I found some confusing parts...

Under section "5.12.4 Using the @Configuration annotation" it says:

"When @Beans have dependencies on one another, expressing that dependency is as simple as having one bean method call another:

@Configuration
public class AppConfig {
    @Bean
    public Foo foo() {
        return new Foo(bar());
    }
    @Bean
    public Bar bar() {
        return new Bar();
    }
}

In the example above, the foo bean receives a reference to bar via constructor injection."

Well, if everything is stateless, it might not matter much, but if you have the config above, and then in your application do:

@Autowired 
private Foo foo;

@Autowired 
private Bar bar;

checking the hashCodes of the beans, it turns out, that your private variable bar will refer to a different instance of Bar than the one used by foo, which is probably not what you expect, right?

I would say the normal pattern should rather be:

@Configuration
public class AppConfig {
    @Bean
    public Bar bar() {
        return new Bar();
    }
    @Autowired Bar bar;
    @Bean
    public Foo foo() {
        return new Foo(bar);
    }
}

Now, when you autowire both beans in your app, you will only create a single instance of Bar.

Am I missing something, or am I correct that the documentation is flaky here?

Then, further down, under section "Further information about how Java-based configuration works internally" it looks like they try to "clarify" this issue:

@Configuration
public class AppConfig {
    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }
    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }
    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }
}

Now, unfortunately, this configuration, won't even load at runtime, because there are 2 beans of the same type, ClientService, with no distinguishing properties, so get exception

org.springframework.beans.factory.NoUniqueBeanDefinitionException: 
No qualifying bean of type [.....] is defined: 
expected single matching bean but found 2

But even if we change the example slightly, and give the first 2 beans different types,

@Bean
public ClientService1 clientService1() {...clientDao()...}
@Bean
public ClientService2 clientService2() {...clientDao()...}
@Bean
public ClientDao clientDao() {
    return new ClientDaoImpl();
}

this is still incorrect, since -- contrary to what the text claims -- we would still create 3 different instances of ClientDaoImpl, when autowiring all 3 beans.

Again, am I totally missing something, or is the documentation really as bad as it seems to me?

EDIT: Added a demo that will demonstrate the issue I see:

https://github.com/rop49/demo

A bean ServiceA, and two beans ServiceB1, ServiceB2 that constructor-injects ServiceA.

Then two test-classes Config01Test and Config02Test that are identical except for the configurations. The first test PASSES, the second FAILS because of uniqueness-asserts.

like image 924
Rop Avatar asked Sep 19 '14 22:09

Rop


1 Answers

Check that you have @Configuration annotations on your configuration class. With your demo at least they are missing from the Config01 and Config02 classes. If I add them to those classes the tests pass.

like image 74
Phil Webb Avatar answered Oct 22 '22 10:10

Phil Webb