In a Pro Spring 3 Book, Chapter 4 - Introduction IOC and DI in Spring - Page 59, In "Setter Injection vs. Constructor Injection" section, a paragraph says
Spring included, provide a mechanism for ensuring that all dependencies are defined when you use Setter Injection, but by using Constructor Injection, you assert the requirement for the dependency in a container-agnostic manner"
Could you explain with examples
This question is related to
java
spring
dependency-injection
required
With examples? Here's a simple one:
public class TwoInjectionStyles {
private Foo foo;
// Constructor injection
public TwoInjectionStyles(Foo f) {
this.foo = f;
}
// Setting injection
public void setFoo(Foo f) { this.foo = f; }
}
Personally, I prefer constructor injection when I can.
In both cases, the bean factory instantiates the TwoInjectionStyles
and Foo
instances and gives the former its Foo
dependency.
A class that takes a required dependency as a constructor argument can only be instantiated if that argument is provided (you should have a guard clause to make sure the argument is not null.) A constructor therefore enforces the dependency requirement whether or not you're using Spring, making it container-agnostic.
If you use setter injection, the setter may or may not be called, so the instance may never be provided with its dependency. The only way to force the setter to be called is using @Required
or @Autowired
, which is specific to Spring and is therefore not container-agnostic.
So to keep your code independent of Spring, use constructor arguments for injection.
Update: Spring 4.3 will perform implicit injection in single-constructor scenarios, making your code more independent of Spring by potentially not requiring an @Autowired
annotation at all.
This example may help:
Controller class:
@RestController
@RequestMapping("/abc/dev")
@Scope(value = WebApplicationContext.SCOPE_REQUEST)
public class MyController {
//Setter Injection
@Resource(name="configBlack")
public void setColor(Color c) {
System.out.println("Injecting setter");
this.blackColor = c;
}
public Color getColor() {
return this.blackColor;
}
public MyController() {
super();
}
Color nred;
Color nblack;
//Constructor injection
@Autowired
public MyController(@Qualifier("constBlack")Color b, @Qualifier("constRed")Color r) {
this.nred = r;
this.nblack = b;
}
private Color blackColor;
//Field injection
@Autowired
private Color black;
//Field injection
@Resource(name="configRed")
private Color red;
@RequestMapping(value = "/customers", produces = { "application/text" }, method = RequestMethod.GET)
@ResponseStatus(value = HttpStatus.CREATED)
public String createCustomer() {
System.out.println("Field injection red: " + red.getName());
System.out.println("Field injection: " + black.getName());
System.out.println("Setter injection black: " + blackColor.getName());
System.out.println("Constructor inject nred: " + nred.getName());
System.out.println("Constructor inject nblack: " + nblack.getName());
MyController mc = new MyController();
mc.setColor(new Red("No injection red"));
System.out.println("No injection : " + mc.getColor().getName());
return "Hello";
}
}
Interface Color:
public interface Color {
public String getName();
}
Class Red:
@Component
public class Red implements Color{
private String name;
@Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Red(String name) {
System.out.println("Red color: "+ name);
this.name = name;
}
public Red() {
System.out.println("Red color default constructor");
}
}
Class Black:
@Component
public class Black implements Color{
private String name;
@Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Black(String name) {
System.out.println("Black color: "+ name);
this.name = name;
}
public Black() {
System.out.println("Black color default constructor");
}
}
Config class for creating Beans:
@Configuration
public class Config {
@Bean(name = "configRed")
public Red getRedInstance() {
Red red = new Red();
red.setName("Config red");
return red;
}
@Bean(name = "configBlack")
public Black getBlackInstance() {
Black black = new Black();
black.setName("config Black");
return black;
}
@Bean(name = "constRed")
public Red getConstRedInstance() {
Red red = new Red();
red.setName("Config const red");
return red;
}
@Bean(name = "constBlack")
public Black getConstBlackInstance() {
Black black = new Black();
black.setName("config const Black");
return black;
}
}
BootApplication (main class):
@SpringBootApplication
@ComponentScan(basePackages = {"com"})
public class BootApplication {
public static void main(String[] args) {
SpringApplication.run(BootApplication.class, args);
}
}
Run Application and hit URL: GET 127.0.0.1:8080/abc/dev/customers/
Output:
Injecting setter
Field injection red: Config red
Field injection: null
Setter injection black: config Black
Constructor inject nred: Config const red
Constructor inject nblack: config const Black
Red color: No injection red
Injecting setter
No injection : No injection red
Constructor injection is used when the class cannot function without the dependent class.
Property injection is used when the class can function without the dependent class.
As a concrete example, consider a ServiceRepository which depends on IService to do its work. Since ServiceRepository cannot function usefully without IService, it makes sense to have it injected via the constructor.
The same ServiceRepository class may use a Logger to do tracing. The ILogger can be injected via Property injection.
Other common examples of Property injection are ICache (another aspect in AOP terminology) or IBaseProperty (a property in the base class).
To make it simple, let us say that we can use constructor based dependency injection for mandatory dependencies and setter based injection for optional dependencies. It is a rule of thumb!!
Let's say for example.
If you want to instantiate a class you always do it with its constructor. So if you are using constructor based injection, the only way to instantiate the class is through that constructor. If you pass the dependency through constructor it becomes evident that it is a mandatory dependency.
On the other hand, if you have a setter method in a POJO class, you may or may not set value for your class variable using that setter method. It is completely based on your need. i.e. it is optional. So if you pass the dependency through setter method of a class it implicitly means that it is an optional dependency. Hope this is clear!!
(...) by using Constructor Injection, you assert the requirement for the dependency in a container-agnostic manner
This mean that you can enforce requirements for all injected fields without using any container specific solution.
With setter injection special spring annotation @Required
is required.
@Required
Marks a method (typically a JavaBean setter method) as being 'required': that is, the setter method must be configured to be dependency-injected with a value.
Usage
import org.springframework.beans.factory.annotation.Required;
import javax.inject.Inject;
import javax.inject.Named;
@Named
public class Foo {
private Bar bar;
@Inject
@Required
public void setBar(Bar bar) {
this.bar = bar;
}
}
All required fields are defined in constructor, pure Java solution.
Usage
import javax.inject.Inject;
import javax.inject.Named;
@Named
public class Foo {
private Bar bar;
@Inject
public Foo(Bar bar) {
this.bar = bar;
}
}
This is especially useful in Unit Testing. Such kind of tests should be very simple and doesn't understand annotation like @Required
, they generally not need a Spring for running simple unit test. When constructor is used, setup of this class for testing is much easier, there is no need to analyze how class under test is implemented.
By using Constructor Injection, you assert the requirement for the dependency in a container-agnostic manner
We need the assurance from the IoC container that, before using any bean, the injection of necessary beans must be done.
In setter injection strategy, we trust the IoC container that it will first create the bean first but will do the injection right before using the bean using the setter methods. And the injection is done according to your configuration. If you somehow misses to specify any beans to inject in the configuration, the injection will not be done for those beans and your dependent bean will not function accordingly when it will be in use!
But in constructor injection strategy, container imposes (or must impose) to provide the dependencies properly while constructing the bean. This was addressed as " container-agnostic manner", as we are required to provide dependencies while creating the bean, thus making the visibility of dependency, independent of any IoC container.
Edit:
Q1: And how to prevent container from creating bean by constructor with null
values instead of missing beans?
You have no option to really miss any <constructor-arg>
(in case of Spring), because you are imposed by IoC container to provide all the constructor arguments needed to match a provided constructor for creating the bean. If you provide null
in your <constructor-arg>
intentionally. Then there is nothing IoC container can do or need to do with it!
Source: Stackoverflow.com