依赖

Spring框架依赖处理细节

依赖注入:对象通过通过构造器或工厂方法参数或实例化之后通过方法设置的过程定义依赖。

分为两种:基于构造器的依赖注入和基于setter方法的依赖注入。

选择哪种方式:使用构造器确定强制要求的依赖,setter方法用于可选的依赖。

@Required注解作用于setter方法也可以获得强制的效果,但是构造器注入是编程级别的,不容易出问题。

Spring团队拥护构造器注入。构造器注入总是返回完全初始化的状态。

setter方法注入主要用于可选的依赖,而且有合适的默认值,否则就要到处进行not-null检查。

一个使用setter方法的好处是可以在初始化之后重新设置或重新注入依赖。

Constructor-based

基于构造器的依赖注入:容器调用构造函数,并提供相对应数量的参数,每个参数对应一个依赖。

和通过调用静态工厂方法并提供一定数量参数差不多等价,因为参数用法都类似。

构造器参数解析:通过参数类型判断,如果类型一样就按顺序一一对应。

构造器参数类型匹配:可以在<constructor-arg/>元素里提供type属性明确声明参数类型:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

构造器参数索引:可以在<constructor-arg/>元素提供index属性指定参数位置,从0开始计算:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

构造器参数名:通过构造函数参数的名字区分:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>

Setter-based:<bean/>元素的<property/>子元素设置,name属性是bean类里面setXXXX方法的XXXX:

<bean id="exampleBean" class="examples.ExampleBean">
    <property name="stringProperty" value="valueName"/>
    <property name="integerProperty" value="1"/>
</bean>
public class ExampleBean {

    private String stringProperty;
    private int i;

    public void setStringProperty(String stringProperty) {
        this.stringProperty = stringProperty;
    }

    public void setIntegerProperty(int i) {
        this.i = i;
    }
}

<property/>子元素也有ref属性,可以引用别的bean。

依赖解析过程

容器进行以下操作:

ApplicationContext被创建,并用描述所有bean信息的配置元数据初始化。 配置元数据可以是Java注解、xml文件或Java代码;
对于每个bean,它们的依赖被表达成构造器参数、静态工厂方法参数或者属性,当bean被创建的时候提供给bean;
每个属性或参数都是实际要设置的值,或者对容器内别的bean的引用;
每个属性或参数从声明的格式转换成实际的方法参数类型;

Spring默认可以将String转换成所有的内置类型,例如int, long, String, boolean等。

当容器被创建的时候会验证bean的配置。bean属性在创建前不会设置。

singleton作用域(默认)的bean会在容器创建时被创建,否则只有当bean被请求的时候才创建。

依赖和配置细节:

Spring的xml配置元数据文件的<property/><constructor-arg/>元素子元素。

直接值,如原始类型、String等

通过<property/>元素的value属性表示:

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <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="misterkaoli"/>
</bean>

有可以引用id的idref元素:

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

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

直接引用别的bean的ref元素:

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

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

作用域和验证取决ref元素的beanparent属性。

通过parent属性声明bean是引用当前容器的父容器中的bean。

The target bean must be in a parent container of the current one.

内部bean<property/><constructor-arg/>元素内的<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或者name或者scope属性,就算声明了也不生效。

不存在独立访问内部bean,也不存在将内部bean注入别的合作bean。

注入集合

<list/><set/><map/>, 和<props/>元素设置对应的Collection类型的属性和参数。

例如:

<bean id="moreComplexObject" class="example.ComplexObject">
    <!-- (java.util.Properties)类型调用 -->
    <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>
    <!-- (java.util.List)类型调用 -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- (java.util.Map)类型调用 -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key ="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- (java.util.Set)类型调用 -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>

集合迁移

定义一个抽象bean,里面有已经定义的集合,可以通过继承添加或覆盖原有的元素:

<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">
            <props merge="true">
                <prop key="sales">sales@example.com</prop>
                <prop key="support">support@example.co.uk</prop>
            </props>
        </property>
    </bean>
<beans>

使用<props/>元素的merge属性即可继承集合。

上面代码片段中adminEmails的内容为:

administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk

集合迁移的限制:不能迁移不同的集合类型,merge属性必须设置在子元素。

强类型集合:可以声明集合里的对象类型:

public class SomeClass {

    private Map<String, Float> accounts;

    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}
<beans>
    <bean id="something" class="x.y.SomeClass">
        <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>

Spring通过反射得知accounts对象的元素类型信息,就会将string值(9.99, 2.75, and 3.99)转换成Float类型而不是Double。

null和空String值:Spring将空的参数或与之类似的当作空String:

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

等价于:

exampleBean.setEmail("");

<null/>元素用于处理null:

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

等价于:

exampleBean.setEmail(null);

嵌套属性名:

<bean id="something" class="things.ThingOne">
    <property name="fred.bob.sammy" value="123" />
</bean>

意思是:something这个bean有个fred属性,fred有bob,bob有sammy,将sammy的值设置成123。

为了能够正常工作,fred和bob不能为null,否则报空指针异常。

depends-on属性:强制1个或多个bean在设置此属性的bean之前初始化:

<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />

ManagerBean在ExampleBean之前初始化。

lazy-init属性:在第一次请求bean的时候创建bean而不是在容器创建时:

<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>

当lazy-initialized bean作为别的sintelon作用域bean的依赖时忽略lazy-init属性。

可以设置容器级别的lazy-init,<beans/>元素的default-lazy-init属性:

<beans default-lazy-init="true">
    <!-- 没有bean会在容器创建时初始化 -->
</beans>

Autowiring(自动装配)一起协作的bean

Spring容器可以自动装配互相协作的bean,通过检查ApplicationContext的内容。

Autowiring的优势:

显著减少需要声明的属性或者构造器参数;
可以按需要更新配置信息;

当使用xml配置元数据时,可以通过<bean/>元素的autowire属性声明自动装配模式:

no默认值,不进行自动装配。引用bean必须使用ref属性;
byName通过变量名设置属性,通过setter方法达成;
byType通过变量的类型判断,只能存在一个同类型bean,否则报致命错误。
如果没有匹配就什么都不会发生;
constructor类似于byType,但是用于构造器参数。如果没有匹配就报致命错误;

byTypeconstructor模式,可以装配数组或者声明类型信息的列表、强类型Map,对于Map类型,key就是bean的名字。

Autowiring的限制和缺点:

明确的propertyconstructor-arg设置总会覆盖autowiring。
而且不可以装配简单的变量类型,如原始类型, Strings,和类,以及它们的数组,这是设计原因;
不如直接装配明确。Spring管理的对象之间的关系没有明确的说明文档;
装配信息对于生成文档的工具可能是不可用的;(像swagger2之类的)
在一个容器里的多个bean定义可能会匹配同一个方法的参数,对于列表类型不是问题,但是对于只要一个值的对象会造成二义性,之后就会抛出异常;

当然,同样也有几个解决方案:

放弃自动装配,转为使用明确装配;
将不想被装配的bean的autowire-candidate属性设置成false;
选一个首选bean,将其primary属性设置成true;
使用可以更精细控制的注解式配置;

从自动装配候选中排除一个bean:

在xml配置元数据中,将<bean/>元素的autowire-candidate属性设置成false。

注意:autowire-candidate属性设计成只影响基于类型的装配,不影响明确的基于命名的装配。

也可以在<beans/>元素内设置default-autowire-candidates属性限制自动装配候选,它接受一个或多个的匹配规则。对于设置了autowire-candidate属性的bean无效。

注意:是防止自动装配到别的bean,而不是禁止这个bean被装配,包括它自己。

方法式注入:

不同作用域的bean相互依赖的问题,可以让一个bean实现ApplicationContextAware接口,当它需要某个bean的时候就从容器调用getBean方法获得需要的bean。但是这样和spring框架耦合度会变高。

留下评论

您的邮箱地址不会被公开。 必填项已用 * 标注