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

来源:互联网 发布:阿里云上海公司地址 编辑:程序博客网 时间:2024/06/05 20:03

  • DI 简介
  • DI 的方式
    • 1 设值注入
      • JavaBean
      • applicationContextxml
      • 测试方法
      • 测试结果
    • 2 构造器注入
      • JavaBean
      • applicationContextxml
      • 测试方法
      • 测试结果
  • DI 的类型
    • 1 其他 bean
      • 11 idref 元素
      • 12 ref 元素
        • 121 ref bean
        • 122 ref local
        • 123 ref parent
    • 2 内部 bean
    • 3 集合
      • 31 常规使用
      • 32 集合整合
      • 33 强类型集合
    • 4 Null 或空字符串
      • 41 空字符串
      • 42 Null
    • 5 复合嵌套属性名

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>

测试方法

@Testpublic 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>

测试方法

@Testpublic 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