Spring核心技术(三)——Spring的依赖及其注入(续)

本文将继续前文,针对依赖注入的细节进行描述

### 依赖注入细节

如前文所述,开发者可以通过定义Bean的依赖的来引用其他的Bean或者是一些值的,Spring基于XML的配置元数据通过支持一些子元素`<property/>`以及`<constructor-arg/>`来达到这一目的。

### 内在值类型(Java Primitives类型,字符串等)

元素`<property/>`有`value`属性来对人友好易读的形式配置一个属性或者构造参数。Spring的便利之处就是用来将这些字符串的值转换成指定的类型。

```

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">

    <!-- results in a setDriverClassName(String) call -->

    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>

    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>

    <property name="username" value="root"/>

    <property name="password" value="masterkaoli"/>

</bean>

```

下面的例子使用的p命名空间,是更为简单的XML配置。

```

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:p="http://www.springframework.org/schema/p"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

    http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"

        destroy-method="close"

        p:driverClassName="com.mysql.jdbc.Driver"

        p:url="jdbc:mysql://localhost:3306/mydb"

        p:username="root"

        p:password="masterkaoli"/>

</beans>

```

上面的XML更为的简洁,但是因为属性的类型是在运行时确定的,而非设计时确定的,所以除非使用[IntelliJ IDEA](http://www.jetbrains.com/idea/)或者[Spring Tool Suite](https://spring.io/tools/sts)这些工具才能在定义Bean的时候自动完成属性配置。当然很推荐使用这些IDE。

开发者也可以定义一个`java.util.Properties`实例,比如:

```

<bean id="mappings"

    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

    <!-- typed as a java.util.Properties -->

    <property name="properties">

        <value>

            jdbc.driver.className=com.mysql.jdbc.Driver

            jdbc.url=jdbc:mysql://localhost:3306/mydb

        </value>

    </property>

</bean>

```

Spring的容器会将`<value/>`里面的文本通过使用JavaBean的`PropertyEditor`机制转换成一个`java.util.Properties`实例。这也是一个捷径,也是一些Spring团队更喜欢使用嵌套的`<value/>`元素而不是`value`属性风格。

**idref元素**

`idref`元素是一种简单的提前校验错误的方式,通过id来关联容器中的其他的Bean的方式。

```

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

<bean id="theClientBean" class="...">

    <property name="targetName">

        <idref bean="theTargetBean" />

    </property>

</bean>

```

上述的Bean的定义在运行时,和如下定义是完全一致的。

```

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

<bean id="client" class="...">

    <property name="targetName" value="theTargetBean" />

</bean>

```

第一种方式是更值得提倡的,因为使用了`idref`标签,会是的容器在*部署阶段*就针对Bean进行校验,确保Bean一定存在。而第二个版本的话,是没有任何校验的。只有实际上引用了Bean `client`,在实例化client的时候才会发现。如果`client`是一个`prototype`的Bean,那么类似拼写之类的错误会在容器部署以后很久才能发现。

> `idref`元素的`local`属性在4.0以后的xsd中已经不再支持了,而是使用了`bean`引用。如果更新了版本的话,需要将`idref local`引用都转换成 `idref bean`即可。

### 引用其他的Bean

`ref`元素在`<constructor-arg/>`或者`<property/>`中的一个终极标签。开发者可以通过这个标签配置一个Bean来引用另一个Bean。当需要引用一个Bean的时候,被引用的Bean会先实例化,然后配置属性,也就是引用的依赖。如果该Bean是单例Bean的话,那么该Bean会早由容器初始化。最终的引用另一个对象的所有引用。Bean的范围以及校验取决于开发者是否通过`bean`,`local`,`parent`这些属性来指定对象的id或者name属性。

通过指定Bean的`bean`属性中的`<ref/>`来指定依赖是最常见的一种方式,可以引用容器或者父容器中的Bean,无论是否在同一个XML文件定义都可以引用。其中`bean`属性中的值可以和其他引用Bean中的`id`属性一致,或者和其中的一个`name`属性一致的。

```

<ref bean="someBean"/>

```

通过指定Bean的`parent`属性会创建一个引用到当前容器的父容器之中。`parent`属性的值可以跟跟目标Bean的`id`属性一致,或者和目标Bean的`name`属性中的一个一致,目标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>

```

> 与`idref`标签一样,`ref`元素中的`local`标签在xsd 4.0以后已经不再支持了,开发者可以通过将已存在的`ref local`改为`ref bean`来完成更新Spring。

### 内部Bean

定义在`<bean/>`元素的`<property/>`或者`<constructor-arg/>`元素之内的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>

```

内部Bean的定义是不需要指定id或者名字的。如果指定了,容器也不会用之作为分别Bean的区分标识。容器同时也会无视内部Bean的`scope`标签:内部Bean *总是* 匿名的,而且 *总是* 随着外部的Bean同时创建的。开发者是无法将内部的Bean注入到外部Bean以外的其他Bean的。

当然,也有可能从指定范围接收到破坏性回调。比如:一个请求范围的内部Bean包含了一个单例的Bean,那么内部Bean实例会绑定到包含的Bean,而包含的Bean允许访问到`request`的`scope`生命周期。这种场景不常见,内部Bean通常只是共享它的外部Bean。

### 集合

在`<list/>`,`<set/>`,`<map/>`和`<props/>`元素中,开发者可以配置Java集合类型`List`,`Set`,`Map`以及`Properties`的属性和参数。

```

<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,或者是集合的value都可以配置为下列之中的一些元素:

```

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

```

**集合合并**

Spring的容器也支持来合并集合。开发者可以定义一个父样式的`<list/>`,`<map/>`,`<set/>`或者`<props/>`,同时有子样式的`<list/>`,`<map/>`,`<set/>`或者`<props/>`继承并且覆盖父集合。也就是说,子集合的值是父元素和子元素集合的合并值。比如下面的例子。

```

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

```

可以发现,我们在`child`Bean上使用了`merge=true`属性。当`child`Bean由容器初始化且实例化的时候,其实例中包含的`adminEmails`集合就是`child`的`adminEmails`以及`parent`的`adminEmails`集合。如下:

```

administrator=administrator@example.com

sales=sales@example.com

support=support@example.co.uk

```

`child`的`Properties`集合的值继承了`parent`的`<props/>`,`child`的值也支持重写`parent`的值。

这个合并的行为和`<list/>`,`<map/>`以及`<set/>`之类的集合类型的行为是类似的。`<list/>`的特定的例子中,与`List`集合类型类似,有隐含的`ordered`概念的。所有的父元素里面的值,是在所有孩子元素的值之前的。但是像`Map`,`Set`或者`Properties`的集合类型,是不存在顺序的。

**集合合并的限制**

开发者是不能够合并不同类型的集合的(比如`Map`和`List`合并),如果开发者这么做,会抛出异常。`merge`的属性是必须特指到更低级或者继承者,子节点的定义上。特指`merge`属性到父集合的定义上是冗余的,而且在合并上也没有任何效果。

**强类型集合**

在Java 5以后,开发者可以使用强类型的集合了。也就是,开发者可以声明一个`Collection`类型,然后这个集合只包含`String`元素(举例来说)。如果开发者通过Spring来注入强类型的`Collection`到Bean中,开发者就可以利用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>

```

当`foo`的属性`accounts`准备注入的时候,`accounts`的泛型信息`Map<String, Float>`就会通过反射拿到。这样,Spring 的类型转换系统能够识别不同的类型,如上面的例子`Float`然后将字符串的值`9.99, 2.75`以及`3.99`转换成对应的`Float`类型。

### Null以及空字符串

Spring将会将属性的空参数,直接当成空字符串来处理。下面的基于XML的元数据配置就会将email属性配置为`String`的值为`""`

```

<bean class="ExampleBean">

    <property name="email" value=""/>

</bean>

```

上面的例子和下列JAVA代码是一致的。

```

exampleBean.setEmail("")

```

而`<null/>`元素来处理`null`的值。如下:

```

<bean class="ExampleBean">

    <property name="email">

        <null/>

    </property>

</bean>

```

上面的代码和下面的Java代码是一样的效果:

```

exampleBean.setEmail(null)

```

### XML 快捷方式p命名空间

p命名空间令开发者可以使用`bean`的属性,而不用使用嵌套的`<property/>`元素,就能描述开发者想要注入的依赖。

Spring是支持基于XML的格式化的命名空间扩展的。本节讨论的`beans`的配置都是基于XML的,p命名空间并不是定义在XSD文件,而是定义在Spring Core之中的。

下面展示了两种XML片段是同样的解析结果:第一个使用的标准的XML格式,而第二种使用了p命名空间。

```

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:p="http://www.springframework.org/schema/p"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="classic" class="com.example.ExampleBean">

        <property name="email" value="foo@bar.com"/>

    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"

        p:email="foo@bar.com"/>

</beans>

```

上面的例子在Bean中展示了email属性的定义。这种定义告知Spring这是一个属性的声明。如前面所描述,p命名空间并没有标准模式定义,所以你可以配置属性的名字为依赖名字。

下面的例子包括了2个Bean定义,引用了另外的Bean:

```

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:p="http://www.springframework.org/schema/p"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="john-classic" class="com.example.Person">

        <property name="name" value="John Doe"/>

        <property name="spouse" ref="jane"/>

    </bean>

    <bean name="john-modern"

        class="com.example.Person"

        p:name="John Doe"

        p:spouse-ref="jane"/>

    <bean name="jane" class="com.example.Person">

        <property name="name" value="Jane Doe"/>

    </bean>

</beans>

```

从上述的例子中可以看出,`john-modern`不止包含一个属性,也同时使用了特殊的格式来声明一个引用指向另一个Bean。第一个Bean定义使用的是`<property name="spouse" ref="jane"/>`来创建的Bean引用到另外一个Bean,而第二个Bean的定义使用了`p:spouse-ref="jane"`来作为一个指向Bean的引用。在这个例子中`spouse`是属性的名字,而`-ref`部分表名这个依赖不是直接的类型,而是引用另一个Bean。

> p命名空间并不同标准的XML格式一样灵活。比如,声明属性的引用可能和一些以`Ref`结尾的属性相冲突,而标准的XML格式就不会。Spring团队推荐开发者能够和团队商量一下,要使用哪一种方式,而不要同时使用3种方法。

### XML 快捷方式c命名空间

与p命名空间类似,c命名空间是在Spring 3.1首次引入的,c命名空间允许内联的属性来配置构造参数而不用使用`constructor-arg`元素。

下面就是一个使用了c命名空间的例子:

```

<beans xmlns="http://www.springframework.org/schema/beans"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:c="http://www.springframework.org/schema/c"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="bar" class="x.y.Bar"/>

    <bean id="baz" class="x.y.Baz"/>

    <!-- traditional declaration -->

    <bean id="foo" class="x.y.Foo">

        <constructor-arg ref="bar"/>

        <constructor-arg ref="baz"/>

        <constructor-arg value="foo@bar.com"/>

    </bean>

    <!-- c-namespace declaration -->

    <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/>

</beans>

```

`c:`命名空间使用了和`p:`命名空间相类似的方式(使用了`-ref`来配置引用)。而且,同样的,c命名空间也不是定义在XSD的模式之中(但是在Spring Core之中)。

在少数的例子之中,构造函数的参数名字并不可用(通常,如果字节码没有debug信息的编译),开发者可以使用下面的例子:

```

<!-- c-namespace index declaration -->

<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>

```

> 根据XML语法,索引的概念的存在要求使用`_`作为XML属性名字不能以数字开始。

实际上,构造函数的解析机制在匹配参数是很高效的,除非必要,Spring团队推荐在配置中使用命名空间。

### 混合的属性

开发者可以在配置属性的时候配置混合的属性,只要所有的组件路径(除了最后一个属性名字)不能为`null`。

参考如下的定义。

```

<bean id="foo" class="foo.Bar">

    <property name="fred.bob.sammy" value="123" />

</bean>

```

`foo`有一个`fred`的属性,而其中`fred`属性有一个`bob`属性,而`bob`属性之中有一个`sammy`属性,那么最后这个`sammy`属性会配置为`123`。想要上述的配置能够生效,`fred`属性需要有一个`bob`属性且在`fred`构造只是构造之后不为`null`,否则会抛出`NullPointerException`。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容