Spring学习笔记-依赖注入中setter注入和构造器注入的区别
Spring 依赖注入有两种方式:
构造器注入 (Constructor-based Dependency Injection)
Setter 注入(Setter-based Dependency Injection)
今天再看官方文档对于两者选择时意识到自己并不知道这两者的区别。将自己的学习记录一下。
官方文档解释
对于这两种注入方式的选择,官方文档给出的说明如下。
Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies. Note that use of the @Required annotation on a setter method can be used to make the property be a required dependency; however, constructor injection with programmatic validation of arguments is preferable.
The Spring team generally advocates constructor injection, as it lets you implement application components as immutable objects and ensures that required dependencies are not null. Furthermore, constructor-injected components are always returned to the client (calling) code in a fully initialized state. As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.
Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency. One benefit of setter injection is that setter methods make objects of that class amenable to reconfiguration or re-injection later. Management through JMX MBeans is therefore a compelling use case for setter injection.
Use the DI style that makes the most sense for a particular class. Sometimes, when dealing with third-party classes for which you do not have the source, the choice is made for you. For example, if a third-party class does not expose any setter methods, then constructor injection may be the only available form of DI.
翻译一下后大致的主要观点如下:
- 一般的经验为:构造函数用于强制依赖项,而将 setter 方法或配置方法用于可选依赖项(注意:在 setter 方法上使用 @Required 注释可用于使属性成为必需的依赖项)
- 构造函数好处是允许您将应用程序组件实现为不可变对象,并确保所需的依赖项不为空。
- 大量的构造函数参数是一种难闻的代码气味,这意味着该类可能有太多的职责,应该重构以更好地解决关注点的正确分离。
- setter 注入的一个好处是,setter 方法使该类的对象适合于以后的重新配置或重新注入
好吧,看完后云里雾里。
大佬解释
看了一下其他大佬的博客对这个问题的解释。
文章链接:Spring set注入和构造注入的区别
大意如下:
对于Setter注入:因为Spring调用一个对象的set方法注入前,这个对象必须先被实例化。所以在"使用set方法注入"的情况下Spring会首先调用对象的构造函数。
对于构造器注入:如果发现配置了对象的构造注入,那么Spring会在调用构造函数前把构造函数需要的依赖对象都实例化好,然后再把这些实例化后的对象作为参数去调用构造函数。
主要区别为:
- 在使用构造函数和set方法依赖注入时,Spring处理对象和对象依赖的对象的顺序时不一样的
- 通过构造函数的注入方式其实表达了2个对象间的一种强的依赖关系:组合关系
- 通过set方法注入的方式表达了2个对象间较弱的依赖关系:聚合关系
代码演示
用简单的代码演示一下不同的注入方式对于实例化顺序的影响
大致思路为 User 类中需要传入一个 Name类的实例,分别用Set方法和构造器传入。
构造器注入
User 类
public class User {
private Name name;
public User(Name name) {
System.out.println("User 实例化");
this.name = name;
}
}
Name 类
public class Name {
public Name() {
System.out.println("Name 实例化");
}
}
xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.zhao.pojo.User">
<!--构造器注入-->
<constructor-arg name="name" ref="name"></constructor-arg>
</bean>
<bean id="name" class="com.zhao.pojo.Name"></bean>
</beans>
注意:这里我们在配置文件中把 Name 的 bean 配置放在 User 之后,确保 Name 不会永远先于 User 实例化
测试代码
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
User user = context.getBean("user", User.class);
System.out.println(user);
}
运行结果
Setter 注入
User 类
public class User {
private Name name;
public User() {
System.out.println("User 实例化");
}
public void setName(Name name) {
this.name = name;
}
}
xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.zhao.pojo.User">
<!--setter 注入-->
<property name="name" ref="name"></property>
</bean>
<bean id="name" class="com.zhao.pojo.Name"></bean>
</beans>
Name 类和测试代码不变
运行结果