拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 Spring系列4:依赖注入的2种方式

Spring系列4:依赖注入的2种方式

白鹭 - 2022-03-03 2067 0 0

本文内容

  1. 基于构造器的依赖注入
  2. 基于setter的依赖注入

基于构造器的依赖注入

案例

定义2个简单的bean类,BeanOne 和 BeanTwo,前者依赖后者,

package com.crab.spring.ioc.demo02;

public class BeanTwo {
}
package com.crab.spring.ioc.demo02;

/**
 * @author zfd
 * @version v1.0
 * @date 2022/1/12 16:59
 */
public class BeanOne {
    private int age;
    private String name;
    private BeanTwo beanTwo;
   
    /**
     * 建构式,用于依赖注入,定义3个依赖
     * @param age
     * @param name
     * @param beanTwo
     */
    public BeanOne(int age, String name, BeanTwo beanTwo) {
        this.age = age;
        this.name = name;
        this.beanTwo = beanTwo;
    }

    @Override
    public String toString() {
        return "BeanOne{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", beanTwo=" + beanTwo +
                '}';
    }
}

通过xml组态档实作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="bean2" />

    <bean id="bean1" >
        <constructor-arg name="age" index="0" type="int" value="https://www.cnblogs.com/kongbubihai/p/20"/>
        <constructor-arg name="name" index="1" type="java.lang.String" value="https://www.cnblogs.com/kongbubihai/p/xxx"/>
        <constructor-arg name="beanTwo" index="2" type="com.crab.spring.ioc.demo02.BeanTwo" ref="bean2"/>
    </bean>
</beans>

来个测验类验证下注入

package com.crab.spring.ioc.demo02;
/**
 * @author zfd
 * @version v1.0
 * @date 2022/1/12 17:09
 */
public class demo02Test {

    @Test
    public void test_construct() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo02/spring1.xml");
        BeanOne bean1 = context.getBean("bean1", BeanOne.class);
        System.out.println(bean1);
        context.close();
    }

输出如下

BeanOne{age=20, name='xxx', beanTwo=com.crab.spring.ioc.demo02.BeanTwo@5204062d}

对照组态档BeanOne的3个依赖都通过构造器的方式进行注入了,符合预期,很简单,

constructor-arg详解

标签constructor-arg支持的元素串列如下,

元素名 作用
name 自变量名
index 自变量索引,0开始
type 自变量型别
value
ref bean参考

例如案例中的配置

<bean id="bean1" >
    <constructor-arg name="age" index="0" type="int" value="https://www.cnblogs.com/kongbubihai/p/20"/>
    <constructor-arg name="name" index="1" type="java.lang.String" value="https://www.cnblogs.com/kongbubihai/p/xxx"/>
    <constructor-arg name="beanTwo" index="2" 		            		type="com.crab.spring.ioc.demo02.BeanTwo" ref="bean2"/>
</bean>

注意: 在没有引起歧义的情况下,上面的部分元素并不是都必须配置的,如指定了index时可以定位自变量位置,那么name是可以不配置的,又如通过ref参考依赖bean,type可以省略

<bean id="bean1" >
    <constructor-arg index="0" type="int" value="https://www.cnblogs.com/kongbubihai/p/20"/>
    <constructor-arg index="1" type="java.lang.String" value="https://www.cnblogs.com/kongbubihai/p/xxx"/>
    <constructor-arg  index="2" ref="bean2"/>
</bean>

注意事项

当在<constructor />使用 name元素指定建构式中的自变量名时,尤其要方法自变量名编译后是否保留的情况,举个例子

// 编译前的方法自变量
public BeanOne(int age, String name, BeanTwo beanTwo)
// 编译后的方法自变量
public BeanOne(int var1, String var2, BeanTwo var3)

编译后方法自变量被编译成var1这种形式会导致仅指定name的构造注入失效,

如何解决?提供2种方法

  1. Maven编译插件添加编译自变量,保留自变量名,pom档案下增加如下插件配置

        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <encoding>UTF8</encoding>
                        <compilerArgs>
                            <arg>-parameters</arg>
                        </compilerArgs>
                    </configuration>
                </plugin>
    
            </plugins>
        </build>
    
  2. 使用 @ConstructorProperties JDK 注释显式命名建构式自变量

        /**
         * 建构式,用于依赖注入,定义3个依赖
         * @param age
         * @param name
         * @param beanTwo
         */
        @ConstructorProperties({"age", "name", "beanTwo"}) // 显式宣告构造自变量名称
        public BeanOne(int age, String name, BeanTwo beanTwo) {
            this.age = age;
            this.name = name;
            this.beanTwo = beanTwo;
        }
    

基于setter的依赖注入

基于 Setter 的 DI 是通过容器在呼叫无自变量建构式或无自变量静态工厂方法来实体化 bean 后呼叫 bean 上的 setter 方法来完成的,

来一个简单类BeanThree依赖BeanOneBeanTwo并提供了Setter方法来设定,

package com.crab.spring.ioc.demo02;

/**
 * @author zfd
 * @version v1.0
 * @date 2022/1/13 8:18
 */
public class BeanThree {
    private BeanTwo beanTwo;
    private BeanOne beanOne;

    public void setBeanTwo(BeanTwo beanTwo) {
        this.beanTwo = beanTwo;
    }

    public void setBeanOne(BeanOne beanOne) {
        this.beanOne = beanOne;
    }
}

对应的组态档可以通过标签property中的refname来设定属性参考或是属性值

<?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="bean2" />

    <!--建构式注入-->
    <bean id="bean1" >
        <constructor-arg name="age" index="0" type="int" value="https://www.cnblogs.com/kongbubihai/p/20"/>
        <constructor-arg name="name" index="1" type="java.lang.String" value="https://www.cnblogs.com/kongbubihai/p/xxx"/>
        <constructor-arg name="beanTwo" index="2" type="com.crab.spring.ioc.demo02.BeanTwo" ref="bean2"/>
    </bean>

    <!--setter注入-->
    <bean id="bean3" >
        <!-- 1 ref元素-->
        <property name="beanOne" ref="bean1"></property>
        <!-- 2 ref标签-->
        <property name="beanTwo">
            <ref bean="bean2"></ref>
        </property>
         <property name="name" value="https://www.cnblogs.com/kongbubihai/p/xxxx"/>
    </bean>
</beans>

运行测验类和结果,可见依赖注入成功

public class demo02Test {

    @Test
    public void test_construct() {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo02/spring1.xml");
        BeanOne bean1 = context.getBean("bean1", BeanOne.class);
        System.out.println(bean1);

        System.out.println("演示Setter注入");
        BeanThree beanThree = context.getBean(BeanThree.class);
        System.out.println(beanThree);
        context.close();
    }
}
BeanOne{age=20, name='xxx', beanTwo=com.crab.spring.ioc.demo02.BeanTwo@68ceda24}
演示Setter注入
BeanThree{name='xxxx', beanTwo=com.crab.spring.ioc.demo02.BeanTwo@68ceda24, beanOne=BeanOne{age=20, name='xxx', beanTwo=com.crab.spring.ioc.demo02.BeanTwo@68ceda24}}

依赖解决程序

  • ApplicationContext是用描述所有bean的配置元资料创建和初始化的,配置元资料可以通过XML、Java代码或注释指定,
  • 对于每个bean,它的依赖项都以属性、建构式自变量或静态工厂方法的自变量的形式表示(如果使用静态工厂方法而不是普通建构式的话),当bean实际创建时,这些依赖项被提供给bean,
  • 每个属性或建构式自变量都是要设定的值的实际定义,或者是对容器中另一个bean的参考,
  • 每个具有值的属性或建构式自变量都将从其指定的格式转换为该属性或建构式自变量的实际型别,默认情况下,Spring可以将字符串格式提供的值转换为所有内置型别,如int、long、string、boolean等,

萝卜青菜各有所爱

选择用构造器注入还是Setter注入?

Spring官方的推荐,建构式用于强制依赖项,将 setter 方法或配置方法用于可选依赖项,请注意,在 setter 方法上使用 @Required 注释可用于使属性成为必需的依赖项;然而,带有自变量的编程验证的建构式注入是更可取的,

Spring 团队通常提倡建构式注入,因为它允许您将应用程序组件实作为不可变物件,并确保所需的依赖项不为空,此外,建构式注入的组件总是以完全初始化的状态回传给客户端(呼叫)代码,作为旁注,大量的建构式自变量是一种不好的代码气味,这意味着该类可能有太多的职责,应该重构以更好地解决适当的关注点分离, Setter 注入应该主要只用于可以在类中分配合理默认值的可选依赖项,否则,必须在代码使用依赖项的任何地方执行非空检查, setter 注入的一个好处是 setter 方法使该类的物件可以在以后重新配置或重新注入,

有时,在处理没有源代码的第三方类时,如果第三方类没有公开任何 setter 方法,那么建构式注入可能是 DI 的唯一可用形式,

总结

本文演示2种依赖注入的方式:建构式注入和Setter方法注入,并对比如何选择这2种方式,下一篇继续深入依赖注入,

知识分享,转载请注明出处,学无先后,达者为先!

标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *