Spring 核心技术IoC容器(二)

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

依赖

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

依赖注入

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

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

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

基于构造函数的依赖注入

基于构造函数的依赖注入是由容器来调用构造函数,构造函数的参数代表这个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定义的构造函数参数,然后构造器参数中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无法决定值得类型,所以无法匹配的。考虑代码如下:

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对象,不依赖于任何容器的特殊的接口,基类或者注解。

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-10-08 08:29:08

Spring 核心技术IoC容器(二)的相关文章

Spring核心技术IoC容器(八)

本文针对自动装载的一些注解进行描述. 基于注解的容器配置 @Required注解 @Required注解需要应用到Bean的属性的setter方法上面,如下面的例子: public class SimpleMovieLister { private MovieFinder movieFinder; @Required public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } //

Spring核心技术IoC容器(六)

前文已经描述了Bean的作用域,本文将描述Bean的一些生命周期作用,配置还有Bean的继承. 定制Bean 生命周期回调 开发者通过实现Spring的InitializeingBean和DisposableBean接口,就可以让容器来管理Bean的生命周期.容器会调用afterPropertiesSet()前和destroy()后才会允许Bean在初始化和销毁Bean的时候执行一些操作. JSR-250的@PostConstruct和@PreDestroy注解就是现代Spring应用生命周期回

Spring核心技术IoC容器(五)

前文概述了Spring的容器,Bean,以及依赖的一些信息,本文将描述一下Bean的作用域 Bean的作用域 当开发者定义Bean的时候,同时也会定义了具体如何创建Bean实例的步骤.这些步骤是很重要的,因为只有通过这些配置,开发者才能创建实例对象. 开发者不仅可以控制多种多样的依赖到Bean之中,也可以配置Bean的作用域.这种方法是非常强大而且弹性也非常好,开发者可以通过配置来指定对象的作用域,而不用在Java类层次上来配置.Bean可以配置多种作用域.Spring框架支持5中作用域,有三种

Spring 核心技术 IoC容器(一)

IoC 容器 IoC容器和Bean简介 这章包括了Spring框架对于IoC规则的实现.Ioc也同DI(依赖注入).而对象是通过构造函数,工厂方法,或者一些Set方法来定义对象之间的依赖的.容器在创建这些Bean对象的时候同时就会注入这些依赖.这个过程是根本上的反转了,不再由Bean本身来控制实例化和定位依赖,而是通过服务定位来控制这个过程,也是IoC(控制反转)的由来. org.springframework.beans和org.springframework.context包是Spring框

Spring核心技术IoC容器(四)

前面两篇文章描述了IoC容器中依赖的概念,包括依赖注入以及注入细节配置.本文将继续描述玩全部的依赖信息. 使用 depends-on 如果一个Bean是另一个Bean的依赖的话,通常来说这个Bean也就是另一个Bean的属性之一.多数情况下,开发者可以在配置XML元数据的时候使用<ref/>标签.然而,有时Bean之间的依赖关系不是直接关联的.比如:需要调用类的静态实例化器来出发,类似数据库驱动注册.depends-on属性会使明确的强迫依赖的Bean在引用之前就会初始化.下面的例子使用dep

Spring核心技术IoC容器(七)

本文将讨论如何关于在Spring生命周期中扩展Spring中的Bean功能. 容器的扩展 通常来说,开发者不需要通过继承ApplicationContext来实现自己的子类扩展功能.但是Spring IoC容器确实可以通过实现接口来增加一些功能.下面将描述一下这些接口. 通过BeanPostProcessor定义Bean BeanPostProcessor接口定义了一些回调方法,开发者可以通过实现来自己的实例化逻辑,依赖解析逻辑等等.如果开发者只是想在Spring容器完成了实例化,配置以及初始化

Spring 核心技术IoC容器 (三)

本文将继续前文,针对依赖注入的细节进行描述 依赖注入细节 如前文所述,开发者可以通过定义Bean的依赖的来引用其他的Bean或者是一些值.Spring基于XML的配置元数据支持一些子元素<property/>以及<constructor-arg/>来达到这一目的. 内在值类型(Java Primitives类型,字符串等) 元素<property/>有value属性来以易读的形式配置一个属性或者构造参数.Spring的遍历就是用来讲这些字符串的值转换成指定的类型. &l

Spring框架IOC容器和AOP解析

主要分析点: 一.Spring开源框架的简介  二.Spring下IOC容器和DI(依赖注入Dependency injection) 三.Spring下面向切面编程(AOP)和事务管理配置  一.Spring开源框架的简介  Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来.它是为了解决企业应用开

Spring之IOC容器加载初始化的方式

引言 我们知道IOC容器时Spring的核心,可是如果我们要依赖IOC容器对我们的Bean进行管理,那么我们就需要告诉IOC容易他需要管理哪些Bean而且这些Bean有什么要求,这些工作就是通过通过配置文件告诉Spring 的IOC容器.在我们的完成这些配置文件以后,如果IOC容易要实现对这些Bean的管理,除了资源的定位还有一个很重要的一步就是完成IOC加载初始化,也就是配置文件的载入过程.完成IOC容器加载初始化的方式只要有三种,第一种就是通过File文件加载,第二种是通过Classpath