可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
This question already has an answer here:
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
回答1:
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.
回答2:
(...) 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; } }
回答3:
- Partial dependency: can be injected using setter injection but it is not possible by constructor. Suppose there are 3 properties in a class, having 3 arg constructor and setters methods. In such case, if you want to pass information for only one property, it is possible by setter method only.
- Overriding: Setter injection overrides the constructor injection. If we use both constructor and setter injection, IOC container will use the setter injection.
- Changes: We can easily change the value by setter injection. It doesn't create a new bean instance always like constructor. So setter injection is flexible than constructor injection
回答4:
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!!
回答5:
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).
回答6:
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
回答7:
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!
回答8:
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.