So since I've been using Spring, if I were to write a service that had dependencies I would do the following:
@Component
public class SomeService {
@Autowired private SomeOtherService someOtherService;
}
I have now run across code that uses another convention to achieve the same goal
@Component
public class SomeService {
private final SomeOtherService someOtherService;
@Autowired
public SomeService(SomeOtherService someOtherService){
this.someOtherService = someOtherService;
}
}
Both of these methods will work, I understand that. But is there some advantage to using option B? To me, it creates more code in the class and unit test. (Having to write constructor and not being able to use @InjectMocks)
Is there something I'm missing? Is there anything else the autowired constructor does besides add code to the unit tests? Is this a more preferred way to do dependency injection?
Yes, option B (which is called constructor injection) is actually recommended over field injection, and has several advantages:
- the dependencies are clearly identified. There is no way to forget one when testing, or instantiating the object in any other circumstance (like creating the bean instance explicitly in a config class)
- the dependencies can be final, which helps with robustness and thread-safety
- you don't need reflection to set the dependencies. InjectMocks is still usable, but not necessary. You can just create mocks by yourself and inject them by simply calling the constructor
See this blog post for a more detailed article, by one of the Spring contributors, Olivier Gierke.
I will explain you in simple words:
In Option(A), you are allowing anyone (in different class outside/inside the Spring container) to create an instance using default constructor (like new SomeService()
), which is NOT good as you need SomeOtherService
object (as a dependency) for your SomeService
.
Is there anything else the autowired constructor does besides add code
to the unit tests? Is this a more preferred way to do dependency
injection?
Option(B) is preferred approach as it does NOT allow to create SomeService
object without actually resolving the SomeOtherService
dependency.
Autowired
constructors provides a hook to add custom code before registering it in the spring container. Suppose SomeService
class extends another class named SuperSomeService
and it has some constructor which takes a name as its argument. In this case, Autowired
constructor works fine. Also, if you have some other members to be initialized, you can do it in the constructor before returning the instance to spring container.
public class SuperSomeService {
private String name;
public SuperSomeService(String name) {
this.name = name;
}
}
@Component
public class SomeService extends SuperSomeService {
private final SomeOtherService someOtherService;
private Map<String, String> props = null;
@Autowired
public SomeService(SomeOtherService someOtherService){
SuperSomeService("SomeService")
this.someOtherService = someOtherService;
props = loadMap();
}
}