Spring 核心技术——IoC 之 DI (1)

1 DI 简介

DI(Dependency Injection)

即依赖注入,提供普通的Java方法让容器去决定依赖关系。容器全权负责的组件的装配,它会把符合依赖关系的对象通过 JavaBean 属性或者构造函数传递给需要的对象。

注意:通常,我们会将 IoC 与 DI 作为同一个概念理解,其实 IoC 是一个很大的概念,DI 只是其中一个比较流行的实现策略。只是对于 Spring 而言,其 IoC 容器就是通过 DI 实现的,所以也就不再严格区分。

2 DI 的方式

在 Spring 中,主要有以下两种方式:

  • 设值注入:通过JavaBean属性注射依赖关系
  • 构造器注入:将依赖关系作为构造函数参数传入

2.1 设值注入

JavaBean

package com.jyhuang.spring.DI.setter_based;

public class People {
    private String name;
    private int age;

    // 使用设值注入,必须提供对应属性的 setter 方法
    // getter...setter

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

applicationContext.xml

使用 <property> 标签进行属性的注入,其中标签属性 name 指定 JavaBean 中对应的属性名,标签属性 value 指定需要注入的值,这两个标签都是必须的。

<bean id="people"  class="com.jyhuang.spring.DI.setter_based.People">
    <property name="name" value="Tom"/>
    <property name="age" value="20"/>
</bean>

测试方法

@Test
public void test() {
    ApplicationContext ctx = new ClassPathXmlApplicationContext(
            "com/jyhuang/spring/DI/setter_based/applicationContext.xml");
    People people = (People) ctx.getBean("people");
    System.out.println(people.toString());
}

测试结果

People{name='Tom', age=20}

2.2 构造器注入

JavaBean

package com.jyhuang.spring.DI.constructor_based;

public class People {
    private String name;
    private int age;

    public People() {}

    // 使用构造器注入,必须提供以对应属性为参数的构造方法
    public People(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // getter...setter

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

applicationContext.xml

使用 <constructor-arg> 标签进行构造器注入。

其中,除了可以通过最常用的标签属性 namevalue 进行注入外,indextype 属性同样可以,这两个属性分别对应构造器中传参的索引和参数类型。三个属性可单独或组合使用,但必须保证最终能精确定位到需要注入值的那个参数。

<property 不同,此标签的 name 属性不是必须的。默认情况下,会根据参数类型自动注入对应的属性,只需要指定 value 即可,即使配置的顺序与构造器的传参顺序不一致,一样能正确注入,但前提是能通过参数类型唯一定位;如果参数类型一致,Spring 会根据配置顺序依次注入。为了避免麻烦,建议配置时手动定位。

<bean id="people"  class="com.jyhuang.spring.DI.constructor_based.People">
    <!--<constructor-arg name="name" value="Tom"/>-->
    <!--<constructor-arg name="age" value="20"/>-->

    <constructor-arg index="0" type="java.lang.String" name="name" value="Tom"/>
    <constructor-arg index="1" type="int" name="age" value="20"/>
</bean>

测试方法

@Test
public void test() {
    ApplicationContext ctx = new ClassPathXmlApplicationContext(
            "com/jyhuang/spring/DI/constructor_based/applicationContext.xml");

    People p = (People) ctx.getBean("people");
    System.out.println(p);
}

测试结果

People{name='Tom', age=20}

3 DI 的类型

除了之前例子中提及到的基本数据类型和 String 等直接值以外,依赖注入的类型还有很多,下面作简要说明。

3.1 其他 bean

注入其他 JavaBean,也叫合作 bean 或者协同 bean,主要有以下两种方式:idrefref,这两个元素都可以作为 <constructor-arg><property> 标签的子元素来指定需要注入的 bean。

3.1.1 idref 元素

<bean id="theTargetBean" class="..."/>

<bean id="theClientBean" class="...">
    <property name="targetName">
        <idref bean="theTargetBean" />
    </property>
</bean>

上面的配置,在运行时完全等价于下面的配置

<bean id="theTargetBean" class="..." />

<bean id="client" class="...">
    <property name="targetName" value="theTargetBean" />
</bean>

idref beanproperty value 注入的值都是字符串类型,只是 idref 注入的是容器中已经存在的 bean 的 id 值,该元素在容器发布时,会对注入的值进行校验,判断容器中是否真的存在这样一个 bean,该 bean 的 id 与 idref bean 配置值相同,而 property value 则会直接把值作为字符串注入。

注意:idref 注入的是字符串类型的值,只是这个值必须对应某个 bean 的 id 值,它并不是真地注入一个 bean,所以,接收该值的属性也必须是字符串。

另外,在 4.0 版本之前,idref 有个 local 属性,但从 4.0 的 beans xsd 开始已经不再被支持。当升级版本时,只需要简单地将 idref local 修改成 idref bean 即可。

3.1.2 ref 元素

idref 元素不同,ref 元素注入的是 bean 实例,而且,ref bean 的值不仅可以是其他 bean 的 id 属性值,还可以是 name 属性值。

3.1.2.1 ref bean
<ref bean="someBean"/>
3.1.2.2 ref local

这个和 idref 一样,从 4.0 的 beans xsd 开始,Spring 就不再支持,此处不再说明。

3.1.2.3 ref parent

通过使用 ref parent 来引用当前容器的父容器中的 bean。使用 parent 属性的主要用途是为了用某个与父容器中的 bean 同名的代理来包装父容器中的一个 bean (例如,子上下文中的一个 bean 定义覆盖了他的父 bean)。

<!-- in the parent context -->
<bean id="accountService" class="com.foo.SimpleAccountService">
    <!-- insert dependencies as required as here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
        <ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
    </property>
    <!-- insert other configuration and dependencies as required here -->
</bean>

另外,ref 不仅可以作为 <constructor-arg><property> 的子标签,还可以作为属性进行 bean 的注入,此时,就等价于 ref bean

<property name="target" ref="someBean"/>

3.2 内部 bean

使用 <bean/> 标签在 <constructor-arg><property> 中定义内部 bean。此时,无须为该内部 bean 指定 id/name 属性,即使指定,容器也会忽略,因为该内部 bean 只能给它对应的外部 bean 使用。另外,对于内部 bean 的 scope 属性,容器也会忽略,因为内部 bean 永远都是匿名的,且随着外部 bean 的创建而创建。

<bean id="outer" class="...">
    <!-- instead of using a reference to a target bean, simply define the target bean inline -->
    <property name="target">
        <bean class="com.example.Person"> <!-- this is the inner bean -->
            <property name="name" value="Fiona Apple"/>
            <property name="age" value="25"/>
        </bean>
    </property>
</bean>

3.3 集合

Spring 主要提供了如下类型的集合注入

  • List: <list/>
  • Set: <set/>
  • Map: <map/>
  • Properties: <props/>

3.3.1 常规使用

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- results in a setAdminEmails(java.util.Properties) call -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">administrator@example.org</prop>
            <prop key="support">support@example.org</prop>
            <prop key="development">development@example.org</prop>
        </props>
    </property>
    <!-- results in a setSomeList(java.util.List) call -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- results in a setSomeMap(java.util.Map) call -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key ="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- results in a setSomeSet(java.util.Set) call -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>

其中,map 的 key/value 和 set 的 value 还可以通过下面任意一种元素指定:

bean | ref | idref | list | set | map | props | value | null

3.3.2 集合整合

集合整合实际就是集合继承,Spring 是支持一个应用定义父类型的集合元素,然后使用子类型的集合元素去继承和覆盖父类型集合元素中的值,只需要在子集合定义时显示的指定 merge=true,适用于上述列举的四种集合类型。

<beans>
    <bean id="parent" abstract="true" class="example.ComplexObject">
        <property name="adminEmails">
            <props>
                <prop key="administrator">administrator@example.com</prop>
                <prop key="support">support@example.com</prop>
            </props>
        </property>
    </bean>
    <bean id="child" parent="parent">
        <property name="adminEmails">
            <!-- the merge is specified on the child collection definition -->
            <props merge="true">
                <prop key="sales">sales@example.com</prop>
                <prop key="support">support@example.co.uk</prop>
            </props>
        </property>
    </bean>
<beans>

注意:在整合中,如果父子集合类型不一致,是不能整合的。

3.3.3 强类型集合

从 Java 5 开始引入了泛型,所以,对于指定了元素类型的集合注入,需要注意类型匹配问题。由于 Spring 自己提供了基本类型转换支持,所以,默认情况下,Spring 会根据目标类型对注入的值进行自动转换。

public class Foo {

    private Map<String, Float> accounts;

    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}
<beans>
    <bean id="foo" class="x.y.Foo">
        <property name="accounts">
            <map>
                <entry key="one" value="9.99"/>
                <entry key="two" value="2.75"/>
                <entry key="six" value="3.99"/>
            </map>
        </property>
    </bean>
</beans>

此例中,当注入 9.992.753.99 时,都会自动转换成 Float 类型。

3.4 Null 或空字符串

3.4.1 空字符串

<bean class="ExampleBean">
    <property name="email" value=""/>
</bean>

上面的配置等价于

exampleBean.setEmail("")

3.4.2 Null

null 值的注入比较特殊,需要使用 <null/> 元素来完成。

<bean class="ExampleBean">
    <property name="email">
        <null/>
    </property>
</bean>

上面的配置等价于

exampleBean.setEmail(null)

3.5 复合/嵌套属性名

Spring 支持 对象.属性 这种嵌套的方式进行属性值的注入,但必须保证在嵌套过程的对象/属性必须存在且不能为 null,否则,会抛出异常。

<bean id="foo" class="foo.Bar">
    <property name="fred.bob.sammy" value="123" />
</bean>

上例中,Bar 必须有 fred 对象作为属性,fred 对象必须有 bob 对象作为属性,bob 对象必须有 sammy 对象作为属性;在注入 123 之前,Barfredbob 对象必须已经初始化,不能为 null,否则会抛出 NullPointerException

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值