2

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

3 Answers 3

2

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.

Sign up to request clarification or add additional context in comments.

Comments

1

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.

2 Comments

Thanks for bringing up the Named annotation - I was wondering how to qualify things like that without using the spring annotations, given that JSR-330's Qualifier couldn't be used on the parameter
Anytime. I hope you can use/mark this as the answer :)
0

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.