romeara
romeara

Reputation: 1516

Spring - Injecting Constructor Arguments in Java Configuration Class

I am currently working on an application which has interdependent services, injected via Spring Java Configuration classes, somewhat like this:

@Configuration
public class ExampleConfiguration {

    @Bean
    public IFirstService firstService() {
        return new FirstServiceImpl();
    }

    @Bean
    public ISecondService secondService() {
        return new SecondServiceImpl();
    }
}

@Service
public class FirstServiceImpl implements IFirstService{
    ...
}

@Service
public class SecondServiceImpl implements ISecondService{

    @Inject
    private IFirstService firstService;

    ...
}

This is working as intended, with a single instance of each service created and injected throughout the application. However, I'm interested in converting to constructor injection - it seems like it would provide better support for unit/mock testing patterns. As I understand it, it would change the SecondServiceImpl code to something like this:

@Service
public class SecondServiceImpl implements ISecondService {

    private IFirstService firstService;

    @Inject
    public SecondServiceImpl(IFirstService firstService){
        this.firstService = firstService;
    }

    ...
}

The problem I'm running into is determining how this interacts/works with the Configuration class above. All the examples I've seen of this do something like:

@Configuration
public class ExampleConfiguration {

    @Bean
    public IFirstService firstService() {
        return new FirstServiceImpl();
    }

    @Bean
    public ISecondService secondService() {
        return new SecondServiceImpl(firstService());
    }
}

But this seems like it would defeat the idea that there should be one instance of IFirstService injected throughout the application, since each call to firstService() instantiates a new IFirstService instance.

I don't know if I am missing a detail about how Spring handles such a thing, or going about dependency injection wrong. Any suggestions would be appreciated!

EDIT:

While the accepted answer is correct, I have recently discovered there is a more robust way to do this - you can specify the desired item as a parameter on the method annotated with @Bean, and it will be injected from the same or other available configurations. So the above would become:

@Configuration
public class ExampleConfiguration {

    @Bean
    public IFirstService firstService() {
        return new FirstServiceImpl();
    }

    @Bean
    public ISecondService secondService(IFirstService firstService) {
        return new SecondServiceImpl(firstService);
    }
}

Note that @Qualifier annotations can be used in-line with the member parameters if a particular bean id is needed

Upvotes: 2

Views: 5925

Answers (3)

luis.espinal
luis.espinal

Reputation: 10539

The @Qualifier annotation is your friend:

@Configuration
public class ExampleConfiguration {

    @Bean(name="first-service")                       // #1 - put a name on it
    public IFirstService firstService() {
        return new FirstServiceImpl();
    }

    @Bean
    public ISecondService secondService(
                          @Qualifier("first-service") // #2 - inject it here
                          IFirstService service {
        return new SecondServiceImpl(service);
    }
}

Notice that this example uses the default singleton prototype, which might or might not work in all situations. For example, you might need to create a new ISecondService instance every time you consult the bean factory but want to use a IFirstService singleton. Or you might want both to act as prototype beans.

Then you will have to make judicious use of scopes for the configuration object as a whole or at specific bean declarations (but now this is another topic altogether.)

EDIT (Use @Named Instead)

Actually, let me expand my response. You can use @Qualifier, but I suggest to use @Named instead as shown below.

@Configuration
public class ExampleConfiguration {

    @Bean(name="first-service")                       // #1 - put a name on it
    public IFirstService firstService() {
        return new FirstServiceImpl();
    }

    @Bean
    public ISecondService secondService(
                          @Named("first-service") // #2 - inject it here
                          IFirstService service {
        return new SecondServiceImpl(service);
    }
}

Why? Well, we have two versions of @Qualifier annotations:

  1. One is the one that comes with JSR-330.
  2. The other is the one with Spring.

JSR-330 @Qualifier cannot be used in parameters as in my example. It is limited to methods and attributes.

The one in my example, however, is the one from Spring, which can be applied to parameters.

In general, we want to stick with JSR-330 annotations (as fine as Spring annotations may be.) So for that, we instead use @Named which is 1) from JSR-330, and 2) it is equivalent to Spring's @Qualifier.

Upvotes: 1

Ken Bekov
Ken Bekov

Reputation: 14015

You config class will not be used as is. Spring will wrap your config into so called proxy-class. This proxy class will intercept all invocations of methods of your original config class, which marked with @Bean annotation. Lets consider code of your configuration:

@Configuration
public class ExampleConfiguration {

    @Bean
    public IFirstService firstService() {
        return new FirstServiceImpl();
    }

    @Bean
    public ISecondService secondService() {
        return new SecondServiceImpl(
              firstService() //actually here is will be invoked method of proxy class
        );
    }
}

Here is firstService() annotated with @Bean. So, because your config class wrapped into proxy, when you call firstService(), it will invoke method of proxy, but not method of original config class.

Simplified logic of proxy class looks as follows. When proxy class intercepts invocations of method, it checks (in case of singletone): is there already created instance of bean. If bean exists, this bean will be returned. If bean does not exists, new instance will be created by invocation of method of original config class.

This means, that each call to firstService() will not instantiates a new IFirstService instance. It will be created just on first call, and same instance will be returned all subsequent calls.

Upvotes: 2

David Fernandez
David Fernandez

Reputation: 585

Spring uses CGLIB proxies to resolve dependency injection during the configuration phase. The result of firstService() is going to be a proxy initially which will then be consolidated as a singleton once all the dependencies have been resolved and injected appropriately.

Upvotes: 0

Related Questions