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

本文将继续前文,描述Spring IoC中的依赖处理。

依赖

一般情况下企业应用不会只有一个对象(或者是Spring Bean)。甚至最简单的应用都要多个对象来协同工作来让终端用户看到一个完整的应用的。下一部分将解释开发者如何从仅仅定义单独的Bean,到让这些Bean在一个应用中协同工作。

依赖注入

依赖注入是一个让对象只通过构造参数,工厂方法的参数或者配置的属性来定义他们的依赖的过程。这些依赖也是对象所需要协同工作的对象。容器会在创建Bean的时候注入这些依赖。整个过程完全反转了由Bean自己控制实例化或者引用依赖,所以这个过程也称之为控制反转

当使用了依赖注入的准则以后,会更易于管理和解耦对象之间的依赖,使得代码更加的简单。对象不再关注依赖,也不需要知道依赖类的位置。这样的话,开发者的类更加易于测试,尤其是当开发者的依赖是接口或者抽象类的情况,开发者可以轻易在单元测试中mock对象。

依赖注入主要使用两种方式,一种是基于构造函数的注入,另一种的基于Setter方法的依赖注入。

基于构造函数的依赖注入

基于构造函数的依赖注入是由IoC容器来调用类的构造函数,构造函数的参数代表这个Bean所依赖的对象。跟调用带参数的静态工厂方法基本一样。下面的例子展示了一个类通过构造函数来实现依赖注入的。需要注意的是,这个类没有任何特殊的地方,只是一个简单的,不依赖于任何容器特殊接口,基类或者注解的普通类。

public class SimpleMovieLister {

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

    // a constructor so that the Spring container can inject a MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

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

}

构造函数的参数解析

构造函数的参数解析是通过参数的类型来匹配的。如果在Bean的构造函数参数不存在歧义,那么构造器参数的顺序也就是就是这些参数实例化以及装载的顺序。参考如下代码:

package x.y;

public class Foo {

    public Foo(Bar bar, Baz baz) {
        // ...
    }

}

假设BarBaz在继承层次上不相关,也没有什么歧义的话,下面的配置完全可以工作正常,开发者不需要再去<constructor-arg>元素中指定构造函数参数的索引或类型信息。

<beans>
    <bean id="foo" class="x.y.Foo">
        <constructor-arg ref="bar"/>
        <constructor-arg ref="baz"/>
    </bean>

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

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

当引用另一个Bean的时候,如果类型确定的话,匹配会工作正常(如上面的例子).当使用简单的类型的时候,比如说<value>true</value>,Spring IoC容器是无法判断值的类型的,所以是无法匹配的。考虑代码如下:

package examples;

public class ExampleBean {

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

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

需要注意的是,做这项工作的代码必须启用了调试标记编译,这样Spring才可以从构造函数查找参数名称。开发者也可以使用@ConstructorProperties注解来显式声明构造函数的名称,比如如下代码:

package examples;

public class ExampleBean {

    // Fields omitted

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

}

基于Setter方式的依赖注入

基于Setter函数的依赖注入则是容器会调用Bean的无参构造函数,或者无参数的工厂方法,然后再来调用Setter方法来实现的依赖注入。

下面的例子展示了使用Setter方法进行的依赖注入,下面的类对象只是简单的POJO对象,不依赖于任何Spring的特殊的接口,基类或者注解。

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方式在通过构造函数注入依赖之后再次注入依赖。开发者在BeanDefinition中可以使用PropertyEditor实例来自由选择注入的方式。然而,大多数的开发者并不直接使用这些类,而是跟喜欢XML形式的bean定义,或者基于注解的组件(比如使用@Component@Controller等)或者在配置了@Configuration的类上面使用@Bean的方法。

基于构造函数还是基于Setter方法?

因为开发者可以混用两者,所以通常比较好的方式是通过构造函数注入必要的依赖通过Setter方式来注入一些可选的依赖。其中,在Setter方法上面的@Required注解可用来构造必要的依赖。

Spring队伍推荐基于构造函数的注入,因为这种方式会促使开发者将组件开发成不可变对象而且确保了注入的依赖不为null。而且,基于构造函数的注入的组件被客户端调用的时候也是完全构造好的。当然,从另一方面来说,过多的构造函数参数也是非常差的代码方式,这种方式说明类貌似有了太多的功能,最好重构将不同职能分离。

基于Setter的注入只是用于可选的依赖,但是也最好配置一些合理的默认值。否则,需要对代码的依赖进行非NULL的检查了。基于Setter方法的注入有一个便利之处在于这种方式的注入是可以进行重配置和重新注入的。

依赖注入的两种风格适合大多数的情况,但是有时使用第三方的库的时候,开发者可能并没有源码,而第三方的代码也没有setter方法,那么就只能使用基于构造函数的依赖注入了。

依赖解析过程

容器对Bean的解析如下:

  • 创建并根据描述的元数据来实例化ApplicationContext。配置元数据可以通过XML, Java 代码,或者注解。
  • 每一个Bean的依赖通过构造函数参数或者属性或者静态工厂方法的参数等来表示。这些依赖会在Bean创建的的时候注入和装载。
  • 每一个属性或者构造函数的参数都是实际定义的值或者引用容器中其他的Bean。
  • 每一个属性或者构造参数可以根据其指定的类型转换而成。Spring也可以将String转成默认的Java内在的类型,比如int,long,String,boolean等。

Spring容器会在容器创建的时候针对每一个Bean进行校验。然而,Bean的属性在Bean没有真正创建的时候是不会配置进去的。单例类型的Bean是容器创建的时候配置成预实例状态的。Bean的Scope在后续有介绍。其他的Bean都只有在请求的时候,才会创建。显然创建Bean对象会有一个依赖的图。这个图表示Bean之间的依赖关系,容器根据此来决定创建和配置Bean的顺序。

循环依赖

如果开发者主要使用基于构造函数的依赖注入,那么很有可能出现一个循环依赖的场景。

比如说:类A在构造函数中依赖于类B的实例,而类B的构造函数依赖类A的实例。如果你这么配置类A和类B相互注入的话,Spring IoC容器会发现这个运行时的循环依赖,并且抛出BeanCurrentlyInCreationException

开发者可以通过使用Setter方法来配置依赖注入,这样可以解决这个问题。或者就不使用基于构造函数的依赖注入,仅仅使用基于Setter方法的依赖注入。换言之,尽管不推荐,但是开发者可以将循环依赖配置为基于Setter方法的依赖注入。

开发者可以相信Spring能正确处理Bean。Spring能够在加载的过程中发现配置的问题,比如引用到不存在的Bean或者是循环依赖。Spring会尽可能晚的在Bean创建的时候装载属性或者解析依赖。这也意味着Spring容器加载正确后会在Bean注入依赖出错的时候抛出异常。比如,Bean抛出缺少属性或者属性不合法。这延迟的解析也是为什么ApplicationContext的实现会令单例Bean处于预实例化状态。这样,通过ApplicationContext的创建,可以在真正使用Bean之前消耗一些内存代价发现配置的问题。开发者也可以覆盖默认的行为让单例Bean延迟加载,而不是处于预实例化状态。

如果不存在循环依赖的话,Bean所引用的依赖会优先完全构造依赖的。举例来说,如果Bean A依赖于Bean B,那么Spring IoC容器会先配置Bean B,然后调用Bean A的Setter方法来构造Bean A。换言之,Bean先会实例化,然后注入依赖,然后才是相关的生命周期方法的调用。

依赖注入的例子

下面的例子会使用基于XML配置的元数据,然后使用Setter方式进行依赖注入。代码如下:

<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"/>
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;
    }

}

在上面的例子当中,Setter方法的声明和XML文件中相一致,下面的例子是基于构造函数的依赖注入

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- constructor injection using the nested ref element -->
    <constructor-arg>
        <ref bean="anotherExampleBean"/>
    </constructor-arg>

    <!-- constructor injection using the neater ref attribute -->
    <constructor-arg ref="yetAnotherBean"/>

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

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
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;
    }

}

在Bean定义之中的构造函数参数就是用来构造ExampleBean的依赖。

下面的例子,是通过静态的工厂方法来返回Bean实例的。

<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"/>
public class ExampleBean {

    // a private constructor
    private ExampleBean(...) {
        ...
    }

    // a static factory method; the arguments to this method can be
    // considered the dependencies of the bean that is returned,
    // regardless of how those arguments are actually used.
    public static ExampleBean createInstance (
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

        ExampleBean eb = new ExampleBean (...);
        // some other operations...
        return eb;
    }

}

工厂方法的参数,也是通过 <constructor-arg/>标签来指定的,和基于构造函数的依赖注入是一致的。之前有提到过,返回的类型不需要跟exampleBean中的class属性一致的,class指定的是包含工厂方法的类。当然了,上面的例子是一致的。使用factory-bean的实例工厂方法构造Bean的,这里就不多描述了。

时间: 2024-12-06 14:16:00

Spring核心技术(二)——Spring的依赖及其注入的相关文章

Spring MVC(二)--Spring MVC登陆实例

本文通过一个简单的登陆实例实现Spring MVC的流程,同时整合 MyBatis使用,流程是这样的: 1.访问一个URL进入登陆界面 2.输入正确的用户名和密码,成功则进入index页面,否则留在登陆页 一.配置web.xml 创建好WEB项目之后的第一步就是配置web.xml文件 1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app version="3.0" xmlns=&

spring boot(二): spring boot+jdbctemplate+sql server

前言 小项目或者做demo时可以使用jdbc+sql server解决即可,这篇就基于spring boot环境使用jdbc连接sql server数据库,和spring mvc系列保持一致. 在spring boot中使用jdbc 连接sql server数据只需要引入两个jar:spring-boot-starter-jdbc.spring-boot-starter-data-jpa 项目结构 和上篇spring boot入门保持相同 pom.xml <dependencies> <

Spring 的核心机制:依赖注入(控制反转)

一.说到依赖注入(控制反转),先要理解什么是依赖. Spring 把相互协作的关系称为依赖关系.假如 A 组件调用了 B 组件的方法,我们可称A 组件依赖于 B 组件. 二.什么是依赖注入. 在传统的程序设计过程中,通常由调用者来创建被调用者的实例. 在依赖注入的模式下,创建被调用者的工作不再由调用者来完成,因此称为控制反转:创建被调用者实例的工作通常由Spring 容器来完成,然后注入给调用者,因此也称为依赖注入. 三.依赖注入的好处. 依赖注入让 Spring 的 Bean 以被指文件组织在

Spring.Net控制翻转、依赖注入、面向切面编程

Spring.Net快速入门:控制翻转.依赖注入.面向切面编程 Spring.Net主要功能: 1.IoC:控制翻转(Inversion of Control)  理解成抽象工厂翻转控制:就是创建对象的权利由开发人员自己控制New,转到了由容器来控制. 2.DI:依赖注入(Dependency Injection)依赖注入:就是在通过容器开创建对象的时候,在对象的初始化是可以给一些属性.构造方法的参数等注入默认值(可以是复杂的类型). 3.AOP:面向切面编程  (类似:管道.MVC过滤器等)

【SSH进阶之路】Spring的IOC逐层深入——依赖注入的两种实现类型(四)

上篇博文,我们介绍了为什么使用IOC容器,和IOC的设计思想以及IOC容器的优缺点,并且给大家转载了一篇介绍IOC原理的博文,我们这篇主要给大家依赖注入的两种方式,以及他们的优缺点. 我们这篇博文还是使用上篇博客中添加用户的实力,只是给大家在注入对象的方式上发生一点点变化,为了让大家更加容易接受.下面我们开始: 构造器注入 构造器注入,即通过构造函数完成依赖关系的设定.我们看一下spring的配置文件: <?xml version="1.0" encoding="UTF

学习spring容器IOC(3)之依赖和依赖注入

学习开涛跟我学spring3 http://jinnianshilongnian.iteye.com/ 为什么要应用依赖注入,应用依赖注入能给我们带来哪些好处呢? 1.动态替换Bean依赖对象,程序更灵活:替换Bean依赖对象,无需修改源文件:应用依赖注入后,由于可以采用配置 文件方式实现,从而能随时动态的替换Bean的依赖对象,无需修改java源文件: 2.更好实践面向接口编程,代码更清晰:在Bean中只需指定依赖对象的接口,接口定义依赖对象完成的功能,通过容 器注入依赖实现: 3.更好实践优

Spring-----3、Spring的核心机制:依赖注入

纵观所有Java应用(从基于Applet的小应用到多层结构的企业级应用),他们都是一种典型的依赖型应用,也就是由一些互相协作的对象构成的.Spring把这种互相协作的关系称为依赖关系.如A组件调用B组件的方法,可称A组件依赖于B组件依赖注入让Spring的Bean以配置文件组织在一起,而不是以硬编码的方式耦合在一起 一.理解依赖注入 依赖注入(Dependency Injection) = 控制反转(Inversion ofControl,IoC):当某个Java实例(调用者)需另一个Java实

(转)Spring读书笔记-----Spring核心机制:依赖注入

Java应用(从applets的小范围到全套n层服务端企业应用)是一种典型的依赖型应用,它就是由一些互相适当地协作的对象构成的.因此,我们说这些对象间存在依赖关系.加入A组件调用了B组件的方法,我们就可以称A组件依赖于B组件.我们通过使用依赖注入,Java EE应用中的各种组件不需要以硬编码方式耦合在一起,甚至无需使用工厂模式.当某个Java 实例需要其他Java 实例时,系统自动提供所需要的实例,无需程序显示获取,这种自动提供java实例我们谓之为依赖注入,也可以称之为控制反转(Inversi

Spring的核心机制:依赖注入

对于一般的Java项目,他们都或多或少有一种依赖型的关系,也就是由一些互相协作的对象构成的.Spring把这种互相协作的关系称为依赖关系.如A组件调用B组件的方法,可称A组件依赖于B组件,依赖注入让Spring的Bean以配置文件组织在一起,而不是以硬编码的方式耦合在一起 一.理解依赖注入 依赖注入(Dependency Injection) = 控制反转(Inversion ofControl,IoC):当某个Java实例(调用者)需另一个Java实例(被调用者)时,在依赖注入模式下,创建被调

Spring 3.0 学习-DI 依赖注入_创建Spring 配置-使用一个或多个XML 文件作为配置文件,使用自动注入(byName),在代码中使用注解代替自动注入,使用自动扫描代替xml中bea

文章大纲 在xml中声明bean和注入bean 在xml中声明bean和自动注入bean 自动扫描bean和自动注入bean 对自动扫描bean增加约束条件 首次接触spring请参考 Spring 3.0 学习-环境搭建和三种形式访问 1.典型的Spring XML 配置文件表头 <?xml version="1.0" encoding="UTF-8"?><!-- 一般化的Spring XML 配置 --> <beans xmlns=