Spring中的依赖关系(DI)


        前言: 最近没事在浏览Spring官网,简单写一些相关的笔记,这篇文章整理Spring依赖注入。并不包含依赖注入所有技术点,只是记录有收获的内容


        最简单的应用程序也有多个对象, 他们一起工作来表示用户所看到的应用程序。依赖注入(Dependency injection即 DI)是一个过程, 在这个过程中,对象通过构造函数的参数、工厂方法的参数或在对象实例后通过设置的属性来定义其依赖项。然后容器在创建bean时注入这些依赖项。

1.什么是依赖入住 DI

        Spring 以“依赖注入”的方式实现了“控制反转"的效果,以便符合软件工程中“依赖倒置”原则。使用依赖注入(Dependency injection即 DI)原则,程序代码更干净,当对象有依赖关系时,解耦更有效。对象不查找其依赖项,也不知道依赖项的位置。测试类变得更容易,特别是当依赖项在接口或抽象基类上时

1.1.基于构造函数

依赖注入(DI)实现一般通过两种方式:基于构造函数的依赖注入和基于Setter的依赖注入, 基于构造函数的DI是通过容器调用一个具有多个参数的构造函数来完成的,每个参数表示一个依赖项。以下示例显示了通过构造函数注入依赖的类:

package x.y;

public class ThingOne {

    public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
        // ...
    }
}

bean定义中定义构造函数“参数的顺序”就是实例化bean时将这些参数提供给相应构造函数的顺序。假设ThingTwo和ThingThree类没有继承关系,以下配置工作正常时,不需要在<constructor arg/>元素中显式指定构造函数参数索引或类型。

<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean>

    <bean id="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>
</beans>

当引用另一个bean时,类型是已知的,并且可以进行匹配,当使用简单类型时,例如<value>true</value>,Spring无法确定值的类型,因此无法在没有帮助的情况下按类型进行匹配,举个栗子,考虑下面的类

package examples;

public class ExampleBean {

    private final int years;
    private final String ultimateAnswer;

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

此时可以使用type属性显式指定构造函数参数的类型

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

还可以使用index属性显式指定构造函数参数的索引,如下例所示

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

索引除了解决多个简单值的模糊性之外,如果构造函数有两个相同类型的参数,则指定索引还可以解决模糊性。但是一定要注意索引是从序号0开始

当然还可以使用构造函数参数名称消除值歧义,如下例所示

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

1.2.基于Setter

基于Setter的DI是在调用无参数构造函数或无参数静态工厂方法来实例化bean之后,通过容器在bean上调用Setter方法来实现的.例如下面传统的java类

public class SimpleMovieLister {
    private MovieFinder movieFinder;

    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
<bean id="simpleMovieLister " class="examples.SimpleMovieLister ">
     <property name="movieFinder" ref="movieFinder"/>
</bean>

<bean id="movieFinder" class="examples.MovieFinder">
</bean>

1.3 选择基于构造&&基于Setter

Spring团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变对象,并确保所需的依赖项不为空。此外,构造函数注入的组件总是以完全初始化状态返回给客户端(调用)代码。作为补充说明,大量构造函数参数是一种糟糕的代码味道,这意味着类可能有太多的责任,应该进行重构以更好地解决关注点的适当分离

Setter注入应该主要用于可在类中分配合理默认值的可选依赖项。否则,在代码使用依赖项的任何地方都必须执行非空检查。setter注入的一个好处是,setter方法使该类的对象能够在以后重新配置或重新注入。因此,通过JMX MBean进行管理是setter注入的一个引人注目的用例

2.依赖注入详细配置

2.1依赖其他的bean

以下示例是基于setter的DI

public class ExampleBean {

    private AnotherBean beanOne;
    private YetAnotherBean beanTwo;
    private int i;

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

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

    public void setIntegerProperty(int i) {
        this.i = i;
    }
}
<bean id="exampleBean" class="examples.ExampleBean">
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

 以下示例使用基于构造函数的DI

public class ExampleBean {

    private AnotherBean beanOne;
    private YetAnotherBean beanTwo;
    private int i;

    public ExampleBean(
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        this.beanOne = anotherBean;
        this.beanTwo = yetAnotherBean;
        this.i = i;
    }


    public static ExampleBean createInstance (
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

        ExampleBean eb = new ExampleBean (...);
        // some other operations...
        return eb;
    }
}
<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg>
        <ref bean="anotherExampleBean"/>
    </constructor-arg>

    <constructor-arg ref="yetAnotherBean"/>

    <constructor-arg type="int" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

现在考虑这个示例,其中Spring被告知调用静态工厂方法以返回对象的实例,而不是使用构造函数:静态工厂方法的参数由<constructor arg/>元素提供,与实际使用构造函数时完全相同

<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
    <constructor-arg ref="anotherExampleBean"/>
    <constructor-arg ref="yetAnotherBean"/>
    <constructor-arg value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>


2.2配置直接获取的值(Straight Values )

<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="misterkaoli"/>
</bean>

2.3集合配置(Collection)

<list/>、<set/>、<map/>和<props/>元素分别设置Java集合类型list、set、map和properties的属性和参数

<bean id="moreComplexObject" class="example.ComplexObject">

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

    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>

    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key="a ref" value-ref="myDataSource"/>
        </map>
    </property>

    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>

2.4空字符串和Null

Spring将属性的空参数等视为空字符串。以下基于XML的配置元数据片段将电子邮件属性设置为空String值

<!-- exampleBean.setEmail(""); -->

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

<null/>元素处理空值 

<!-- exampleBean.setEmail(null); -->

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

2.5属性嵌套

设置bean属性时,可以使用复合或嵌套属性名,只要路径的所有组件(最终属性名除外)都不为空

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

2.6强制依赖depends-on

如果某个bean是另一个bean的依赖项,这通常意味着将一个beam设置为另一个bean的属性。通常,您可以使用基于XML的配置元数据中的<ref/>元素来实现这一点。有时bean之间的依赖关系不那么直接. depends-on属性可以显式地强制一个或多个bean在初始化使用此元素的bean之前进行初始化

例如beanOne强制依赖manager和accountDao

<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
    <property name="manager" ref="manager" />
</bean>

<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />

2.7懒加载Bean

默认情况下,ApplicationContext实现急切地创建和配置所有单例bean,作为初始化过程的一部分。通常,这种预实例化是可取的,因为配置或周围环境中的错误会立即被发现,而不是几小时甚至几天之后。当这种行为不可取时,可以通过将bean定义标记为惰性初始化来防止单例bean的预实例化。惰性初始化bean告诉IoC容器在第一次请求bean时创建bean实例,而不是在启动时创建

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

2.8XML快捷p-namespace

Spring支持具有名称空间的可扩展配置格式,p-名称空间允许您使用bean元素的属性来描述协作bean的属性值

下面的例显示了bean定义中名为email的p命名空间中的属性。这告诉Spring包含一个属性声明。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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="classic" class="com.example.ExampleBean">
        <property name="email" value="someone@somewhere.com"/>
    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="someone@somewhere.com"/>
</beans>

2.9XML快捷c-namespace

和具有p名称空间的XML快捷方式类似,Spring3.1中引入的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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>

    <!-- traditional declaration with optional argument names -->
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg name="thingTwo" ref="beanTwo"/>
        <constructor-arg name="thingThree" ref="beanThree"/>
        <constructor-arg name="email" value="something@somewhere.com"/>
    </bean>

    <!-- c-namespace declaration with argument names -->
    <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
        c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>

</beans>

3.常见问题

3.1 循环依赖

Spring容器在创建容器时验证每个bean的配置。在实际创建bean之前,不会设置bean属性。在创建容器时,单例作用域的bean会被预实例化,而其他的bean只有在请求时才会创建bean。需要注意的是,如果不存在循环依赖项,当一个或多个协作bean被注入到依赖bean中时,每个协作bean在注入到依赖bean之前都会被完全配置。这意味着,如果bean A依赖于bean B,那么Spring IoC容器会在调用bean A上的setter方法之前完全配置bean B

使用构造器注入时可能会创建无法解析的循环依赖场景。bean A和bean B之间的循环依赖关系强制一个bean在被完全初始化之前被注入到另一个bean中,这就是一个典型的先有鸡还是先有蛋的问题。

例如:类A通过构造函数注入需要类B的实例,类B通过构造函数注入要求类A的实例。如果将类A和B的bean配置为相互注入,Spring IoC容器将在运行时检测此循环引用,并抛出BeanCurrentlyInCreateException。

一种解决方案是编辑某些类的源代码,以便由setter而不是构造函数进行配置。或者避免构造函数注入,只使用setter注入。

3.2 属性缺失

Spring在实际创建bean时尽可能晚地设置属性并解析依赖项。这意味着,如果在创建对象或其依赖项时出现问题,正确加载的Spring容器稍后可以在您请求对象时生成异常 — 例如,bean由于缺少属性或属性无效而引发异常


上一篇:Spring中的作用域Bean Scope


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

=PNZ=BeijingL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值