Spring5入门-02-基于构造器和setter的DI(依赖注入)

一、前言

本文目的:了解Spring创建Bean、构造器依赖注入的一些方法。



二、依赖

注意:这里用到的应该是spring-context,但是spring-webmvc由于继承的关系会有一张依赖网:

image-20200922215433508

算是省心省力吧。

<!--Spring-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${org.springframework.version}</version>
</dependency>

<!--JUnit-->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>


三、创建实体类

路径

image-20200923104011243

代码

为了后面看得清楚Spring用的构造方法,在构造方法中加了打印:

User.java:

package com.huangdekai.pojo;

/**
 * @Autord: HuangDekai
 * @Date: 2020/9/23 10:28
 * @Version: 1.0
 * @since: jdk11
 */
public class User {
    private int id;
    private String name;

    public User() {
        System.out.println("User无参构造");
    }

    public User(int id, String name) {
        this.id = id;
        this.name = name;
        System.out.println("User有参构造");
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}


四、配置文档

路径

image-20200923104234249

代码

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="user" class="com.huangdekai.pojo.User">
        <property name="id" value="1" />
        <property name="name" value="Duzhuan" />
    </bean>

</beans>


五、测试样例

路径

image-20200923104320124

代码

import com.huangdekai.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @Autord: HuangDekai
 * @Date: 2020/9/23 10:41
 * @Version: 1.0
 * @since: jdk11
 */
public class UserTest {
    @Test
    public void UserTest(){
        ApplicationContext context = new ClassPathXmlApplicationContext("pojo.xml");
        User user = (User) context.getBean("user");
        System.out.println(user);
    }
}

结果:

image-20200923115001391



六、有参构造

官方文档为此介绍了三种方法(其实是4种,但有一种是注入实体类的,如下所示,先按下不讲):

package x.y;

public class ThingOne {

   public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
       // ...
   }
}
<beans>
   <bean id="beanOne" class="x.y.ThingOne">
       <constructor-arg ref="beanTwo"/>
       <constructor-arg ref="beanThree"/>
   </bean>

   <bean id="beanTwo" class="x.y.ThingTwo"/>

   <bean id="beanThree" class="x.y.ThingThree"/>
</beans>

6.1 构造参数类型匹配(Constructor argument type matching)

配置文件改为:

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">

<!--    <bean id="user" class="com.huangdekai.pojo.User">-->
<!--        <property name="id" value="1" />-->
<!--        <property name="name" value="Duzhuan" />-->
<!--    </bean>-->

    <bean id="user" class="com.huangdekai.pojo.User">
        <constructor-arg type="int" value="1"/>
        <constructor-arg type="java.lang.String" value="Duzhuan"/>
    </bean>

</beans>

很显然,除了基本类型,比较复杂一点的类都需要全限定名

再次运行测试样例:

image-20200923114925477

但是很显然,这是一种十分不推荐的注入方式,就比如,一个类里有多个String,那就不能使用,即使是简单的场景,这种可读性很低的方式也是不推荐的。


6.2 构造参数(Constructor argument index)

再次注释掉前面的内容,写入新的:

<bean id="user" class="com.huangdekai.pojo.User">
     <constructor-arg index="0" value="1"/>
     <constructor-arg index="1" value="Duzhuan"/>
</bean>

很显然,index表示的就是有参构造中,构造参数的顺序序号。从上面也可以看出,index是0开始的。

这种方法比起第一种构造方法的好处就是,当有多个属性都是同一数据类型时候,不冲突。


6.3 构造参数名(Constructor argument name)

再次注释掉上面的内容,写入新代码:

<bean id="user" class="com.huangdekai.pojo.User">
    <constructor-arg name="id" value="1"/>
    <constructor-arg name="name" value="Duzhuan"/>
</bean>

比较推荐使用这种方法。

name的值显然就是User的属性名称。



七、基于setter的依赖注入

官方文档特别提出这一类注入:

Setter-based DI is accomplished by the container calling setter methods on your beans after invoking a no-argument constructor or a no-argument static factory method to instantiate your bean.

即:

基于Setter的依赖注入是由容器调用无参构造函数一个无参静态工程方法后调用bean上的setter方法完成的。

看下面的实例。


7.1 创建一个Role

路径

image-20200923121944226

代码

package com.huangdekai.pojo;

/**
 * @Autord: HuangDekai
 * @Date: 2020/9/23 12:18
 * @Version: 1.0
 * @since: jdk11
 */
public class Role {
    private int id;
    private String roleName;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    @Override
    public String toString() {
        return "Role{" +
                "id=" + id +
                ", roleName='" + roleName + '\'' +
                '}';
    }
}

7.2 修改pojo.xml

image-20200923122052922

代码

将里面的内容全注释掉,只留下基本框架

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">

<!--    <bean id="user" class="com.huangdekai.pojo.User">-->
<!--        <property name="id" value="1" />-->
<!--        <property name="name" value="Duzhuan" />-->
<!--    </bean>-->

<!--    <bean id="user" class="com.huangdekai.pojo.User">-->
<!--        <constructor-arg type="int" value="1"/>-->
<!--        <constructor-arg type="java.lang.String" value="Duzhuan"/>-->
<!--    </bean>-->

<!--        <bean id="user" class="com.huangdekai.pojo.User">-->
<!--            <constructor-arg index="0" value="1"/>-->
<!--            <constructor-arg index="1" value="Duzhuan"/>-->
<!--        </bean>-->

<!--    <bean id="user" class="com.huangdekai.pojo.User">-->
<!--        <constructor-arg name="id" value="1"/>-->
<!--        <constructor-arg name="name" value="Duzhuan"/>-->
<!--    </bean>-->

    <bean id="role" class="com.huangdekai.pojo.Role">
        <property name="id" value="1"/>
        <property name="roleName" value="Duzhuan"/>
    </bean>

</beans>

image-20200923122914585


7.3 测试样例

路径

image-20200923123148521

代码

import com.huangdekai.pojo.Role;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @Autord: HuangDekai
 * @Date: 2020/9/23 12:30
 * @Version: 1.0
 * @since: jdk11
 */
public class RoleTest {
    @Test
    public void roleTest(){
        ApplicationContext context = new ClassPathXmlApplicationContext("pojo.xml");
        Role role = (Role) context.getBean("role");
        System.out.println(role);
    }
}

结果:

image-20200923123646744

有没有觉得很眼熟?

其实这正是一开始的时候的例子,只是这里由于没有有参构造函数,无需显式地写无参构造函数罢了。

如果用IDEA,现在去注释掉Role的set方法,再去看看pojo.xml会有提示错误:

image-20200923123559294

image-20200923123612231



八、一些有趣的东西

有没有一些由于粗心没注释掉某些代码而出现的有趣现象?

我们将pojo.xml中的一个关于user这个bean的方法的注释去掉

<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">

<!--    <bean id="user" class="com.huangdekai.pojo.User">-->
<!--        <property name="id" value="1" />-->
<!--        <property name="name" value="Duzhuan" />-->
<!--    </bean>-->

<!--    <bean id="user" class="com.huangdekai.pojo.User">-->
<!--        <constructor-arg type="int" value="1"/>-->
<!--        <constructor-arg type="java.lang.String" value="Duzhuan"/>-->
<!--    </bean>-->

<!--        <bean id="user" class="com.huangdekai.pojo.User">-->
<!--            <constructor-arg index="0" value="1"/>-->
<!--            <constructor-arg index="1" value="Duzhuan"/>-->
<!--        </bean>-->

    <bean id="user" class="com.huangdekai.pojo.User">
        <constructor-arg name="id" value="1"/>
        <constructor-arg name="name" value="Duzhuan"/>
    </bean>

    <bean id="role" class="com.huangdekai.pojo.Role">
        <property name="id" value="1"/>
        <property name="roleName" value="Duzhuan"/>
    </bean>

</beans>

然后运行RoleTest.java中的roleTest方法:

image-20200923124108352

RoleTest.java中根本就没用到User,但是这还是表示,有一个User对象被创建了。

显然,当我们用ClassPathXmlApplication创建出一个容器的时候,整个配置里设置好的Bean都被创建出来交给容器管理了。



九、关于Spring团队对构造器注入和setter注入的建议

官方文档这个条列的最下面:

Constructor-based or setter-based DI?
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的DI?
由于可以混合使用基于构造器和基于setter的DI,对于强制依赖项使用构造器,而对于可选依赖项使用setter方法或配置方法,这是一个很好的经验法则。请注意,在setter方法上使用@Required注释可以使属性成为必需的依赖项;但是,使用对参数进行编程验证的构造函数注入更可取。
Spring团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变对象,并确保所需的依赖关系不为空。此外,构造器注入的组件总是以完全初始化的状态返回给客户端(调用)代码。顺便提一下,大量的构造函数参数是一种糟糕的代码味道,这意味着类可能有太多的责任,应该进行重构以更好地解决问题的适当分离。
Setter注入应该主要用于可选的依赖项,这些依赖项可以在类中分配合理的默认值。否则,必须在代码使用依赖项的任何地方执行非空检查。setter注入的一个好处是,setter方法使该类的对象易于稍后重新配置或重新注入。因此,通过JMX mbean进行管理是setter注入的一个引人注目的用例。
使用对特定类最有意义的DI样式。有时,在处理没有源代码的第三方类时,需要自己做出选择。例如,如果第三方类不公开任何setter方法,那么构造函数注入可能是DI的唯一可用形式。

posted @ 2020-09-23 12:55  杜撰丶  阅读(321)  评论(0编辑  收藏  举报