Explain why constructor inject is better than other options

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

138784 次浏览

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.

(...) 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.


Setter injection example

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;
}
}

Constructor injection example

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;
}


}

Unit testing

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.

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) (or use a non-nullable type in Kotlin). 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. This applies to tests; you'll have an easier time instantiating and testing the class in a normal unit test, without needing to configure an application context or the complexity that comes along with setting up an integration test.

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.

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!

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!!

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).

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