spring原生依赖注入Dependency Injection

​ 依赖注入(DI)是一个【过程】(目前可以理解为给成员变量赋值的过程),在此过程中,对象仅通过【构造函数参数】、【工厂方法参数】等来确定它们的依赖项。 然后容器在创建bean时注入这些依赖项。 从根本上说,这个过程与bean本身相反(因此得名“控制反转”)。

​ 使用依赖注入的代码更清晰,并且在向对象提供依赖时【解耦更有效】。

DI主要有以下两种方式:

  • Constructor-based依赖注入,基于构造器的依赖注入,本质上是使用构造器给成员变量赋值。
  • Setter-based依赖注入,基于setter方法的依赖注入,本质上是使用set方法给成员变量赋值。

#1、基于构造函数的依赖注入

1、使用参数的顺序实现

如果beanDifination的构造函数参数中不存在【潜在的歧义】,那么在beanDifination中定义【构造函数参数的顺序】就是在实例化bean时将这些参数提供给适当构造函数的顺序,我们可以看一下下边这个类:

package x.y;

public class ThingOne {

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

假设【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>

2、构造函数参数类型匹配

当引用另一个bean时,类型是已知的,可以进行匹配(如上例所示)。 当使用简单类型时,例如<value>true</value> , Spring无法确定值的类型,因此在没有帮助的情况下无法按类型匹配。 考虑以下官网提供的类:

package examples;

public class ExampleBean {

    // Number of years to calculate the Ultimate Answer
    private final int years;

    // The Answer to Life, the Universe, and Everything
    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>

2、基于setter的注入

​ 基于setter的DI是通过容器在【调用无参数构造函数】或【无参数“静态”工厂方法】实例化bean后调用bean上的setter方法来实现的。

​ 下面的示例显示了一个只能通过使用纯setter注入进行依赖注入的类。 这个类是传统的Java。 它是一个POJO,不依赖于容器特定的接口、基类或注解。

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;

    // a setter method so that the Spring container can inject a MovieFinder
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...
}

【ApplicationContext】支持它管理的bean的【基于构造函数】和【基于setter】的依赖注入。 在已经通过构造函数方法注入了一些依赖项之后,它还支持基于setter的DI。也就意味着先通过有参构造构建对象,再通过setter方法进行特殊值的赋值。

下面的元数据配置示例为【基于setter】的DI方式:

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

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

3、基于构造函数还是基于setter的依赖注入?

​ 由于您可以混合使用基于构造函数和基于setter的DI,一般情况下,我们对于【强制性依赖项】使用构造函数,对于【可选依赖项】使用setter方法注入,这是一个很好的经验法则。 注意,在setter方法上使用【@Required】注解可以使属性成为必需依赖项。

​ Spring团队通常提倡构造函数注入,因为它允许你将应用程序组件实现为不可变的对象,并确保所需的依赖项不是”空“的。 而且,构造函数注入的组件总是以完全初始化的状态返回给客户端(调用)代码。

​ Setter注入主要应该只用于可选依赖项,这些依赖项可以在类中分配合理的默认值。 setter注入的一个好处是,setter方法使该类的对象能够在稍后进行重新配置或重新注入。

​ 有时,在处理您没有源代码的第三方类时,您可以自行选择。 例如,如果第三方类不公开任何setter方法,那么构造函数注入可能是DI的唯一可用形式

(7)带有【p命名空间】的XML配置方式

第一步:先在头部文件导入依赖:

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

使用: 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>

8)带有c命名空间的XML快捷方式

与带有p-名称空间的XML配置方式类似,在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
        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>

【c: 命名空间】通过名称设置构造函数参数。 类似地,它需要在XML文件中声明对应的命名空间。

对于【构造函数参数名不可用的罕见情况】(通常是在没有调试信息的情况下编译字节码),可以使用回退参数索引,如下所示:

<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
    c:_2="something@somewhere.com"/>

(10)延迟初始化的 Bean

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

在XML中,这种行为是由<bean/> 元素上的【lazy-init】属性控制的,如下面的示例所示:

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

 然而,当一个【延迟初始化的bean】是一个没有延迟初始化的单例bean的依赖时,ApplicationContext会在启动时创建这个延迟初始化的bean,因为它必须满足单例bean的依赖, 延迟初始化的bean会被注入到没有延迟初始化的其他单例bean中。

你也可以在容器级通过在<beans/>元素上使用“default-lazy-init”属性来控制延迟初始化,如下面的例子所示:

<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值