Spring4参考手册中文版-4.20-5-IoC容器

最新进度参看https://github.com/b2gats/stone-docs/blob/master/spring-4-beans.md

IoC容器

springIOC容器和beans简介

本章讲解spring的控制反转(IoC)的spring 框架实现 [1] 原理. IoC 又名 依赖注入 (DI). 它是一个由对象定义依赖的处理手法,也就是如何与其他对象协同工作, 可以通过以下途径定义依赖:构造参数注入、工厂方法的参数注入、属性注入(是指对象实例化后或者从工厂方法返回一个实例后设置其属性)。容器创建bean时候, 注入 依赖。 这个控制倒转了, 因此得名控制反转 (IoC)。反转了哪些控制,不再是由bean自己控制依赖类的实例化和定位, 而是使用了类似于 服务定位 模式的机制来控制。

org.springframework.beans 和 org.springframework.context 这两个包是spring IOC容器的基础包. BeanFactory 接口 提供了各种配置,用于管理任何对象. ApplicationContext 是 BeanFactory的子接口. 它提供以下功能,使集成Spring’s AOP功能 更容易; 消息资源处理(用于国际化);事件发布;应用层指定上下文环境,像用于web应用的WebApplicationContext.

简而言之, BeanFactory 提供了配置框架和基础功能, ApplicationContext 增加了更多的企业应用用能.ApplicationContext 是 BeanFactory的超集, 是本章的spring IOC示例中的指定容器. 用BeanFactory 替代ApplicationContext, 更多的信息请参看 Section 4.17, “The BeanFactory”.

应用中的对象并且是由spring 容器 管理的,被称为beans.就是对象,由spring容器管理的诸如实例化、组装等等操作. bean可以由应用中的多个对象组成。Bean通过容器和配置元数据 ,使用反射技术,去组装依赖对象。

Container概述

接口org.springframework.context.ApplicationContext代表了srping IoC 容器,负责实例化、配置和组装前面提到的beans。容器依据配置配置元数据去实例化、配置、组装。配置元数据可以用XML、Java 注解、或者Java编码表示。在配置元数据中,可以定义组成应用的对象,以及对象之间的依赖关系。

Spring 提供了一些开箱即用的ApplicationContext接口的实现。在单独的应用中,通常使用ClassPathXmlApplicationContext或者FileSystemXmlApplicationContext。当使用XML定义配置元数据时,可通过一小段xml配置使容器支持其他格式的配置元数据,比如Java 注解、Java Code。

大多数的应用场景中,不需要硬编码来实例化一个Spring IoC 的容器。举个栗子,web应用中,在web.xml中大概8行左右的配置就可以实例化一个Spring Ioc容器(see Section 4.16.4, “Convenient ApplicationContext instantiation for web applications”)。若再有STS(Spring eclipse 套件),简单的钩钩点点即可完成此配置。

下面的示意图是spring工作原理。ApplicationContext将应用中的类与配置元数据相结合,实例化后,即可得到一个可配置、可执行的系统或应用。
The Spring IoC container
spring 工作原理示意图

配置元数据

如上图所示,Spring IoC容器使用某种格式的配置元数据;配置元数据,就是告诉Ioc容器如何将对象实例化、配置、组装。

配置元数据是默认使用简单直观的xml格式,也是本章样例中使用最多的,这些样例程序用以说明spring Ioc 容器的核心概念和功能

注意

基于XML的元数据并不是唯一的格式。Spring IoC容器已经和提及到的元数据格式完全解耦了。目前,很多码农都选择Java-based configuration

欲了解其他元数据格式的使用,请参看:

  • Annotation-based configuration: Spring 2.5 引进的支持java 注解配置元数据
  • Java-based configuration: Spring3.0时,将Spring JavaConfig project的很多功能集成到核心Spring框架中。Thus you can define beans external to your application classes by using Java rather than XML files.你可以使用java配置类定义bean,无需xml,该配置类与应用类无关。想要尝鲜的话,请参看@Configuration,@Bean,@Import,@DependsOn注解

Spring配置由Spring bean的定义组成,这些bean必须被容器管理,至少1个,通常会有多个。基于XML的配置元数据,大概这么配置,根节点<beans>中配置子节点<bean>。Java configuration使用是这样的,一个带有@Configuration类注解的类中,方法上使用@Bean方法注解。

bean的定义要与应用中实际的类相一致。可以定义service 层的对象、Dao对象、类似Struts的表现成的对象、像Hibernate SessionFactories这样的基础对象,JMS队列等等。通常不会去定义细粒度域对象,因为它们由DAO或者Service负责创建、加载。然而,通过集成AspectJ,可以配置非Srping容器创建的对象。参看Using AspectJ to dependency-inject domain objects with Spring

下面的样例展示了基于XML配置元数据的基本格式:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="..." class="...">
    <!-- bean的详细配置 -->
    </bean>

    <bean id="..." class="...">
    <!-- bean的详细配置 -->
    </bean>

    <!-- 其他bean -->

</beans>

id属性是个字串,是bean的唯一标示符。class属性定义了bean的类型,要使用类的全限定类名(含有包路径)。id属性的值,可以作为合作bean的引用标示符。上面未展示如何引用其他对象;详情参看Dependencies

容器实例化

Spring IoC的实例化易如反掌。ApplicationContext构造函数支持定位路径,定位路径也可以是多个,它是标识实际资源的字串,容器使用该标识加载配置元数据,支持多种资源,比如:本地文件系统、CLASSPATH等等。

ApplicationContext context = 
    new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});  

注意
在学习了Spring IoC容器之后,也许你想了解更多的Spring的资源,如前所述在第6章,资源使用URI语法定位输入流,Spring提供了方便的机制读取输入流。在第6.7章“Application contexts and Resource paths”,专门讲述5用 资源路径构造应用上下文,资源路径也是惯用手法。
接下来的样例展示了配置service层对象:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- services -->

    <bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
    <property name="accountDao" ref="accountDao"/>
    <property name="itemDao" ref="itemDao"/>
    <!-- 有关属性配置 -->
    </bean>

    <!--更多的Service bean -->

</beans>

下面的样例展示了数据访问对象dao.xml配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="accountDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
    <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
    <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for data access objects go here -->

</beans>

上述内容中service层由PetStoreServiceImpl类、2个dao对象JpaAccountDaoJpaItemDao(基于JPA ORM标准)。属性name元素引用了JavaBean的属性,ref元素引用了其他bean定义。这个引用表示实际对象之间的引用依赖。配置一个对象的依赖,详情请参看Dependencies

引入基于xml的元数据

多个配置文件共同定义bean非常有用。通常,每个XML配置文件在你的架构中代表一个逻辑层或者一个模块。

你可以使用应用上下文(applicationContext)的构造函数去加载所有xml中定义的bean。这个构造函数使用多个资源定位,就像前面中提到的。或者,也可以用一个或者多个资源引用,即使用<import/>标签加载其他文件定义的bean。举个栗子:

<beans>
    <import resource="services.xml"/>
    <import resource="resources/messageSource.xml"/>
    <import resource="/resources/themeSource.xml"/>

    <bean id="bean1" class="..."/>
    <bean id="bean2" class="..."/>
</beans>

上例中,从三个外部文件加载定义的bean:services.xml,messageSource.xml,themeSource.xml 。被引入的文件的路径对于引入配置文件来说都是相对路径,所以service.xml必须在引入配置文件的相同文件路径或者相同的类路径中。而messageSource.xmlthemeSource.xml必须在引入配置文件所在的文件夹下的resouce文件夹下。正如你所看到的/开头会被忽略掉,因为这些路径是相对路径,推荐不要使用/开头的格式。导入(imported)文件内容,包含根节点<beans/>,配置中XML bean定义 必须经过Spring语法校验通过。

注意

使用"../"表示父目录的相对路径是可以的,但是真心不推荐这样创建一个依赖应用外部文件的做法。尤其指出,使用"classpath:"资源类型的URLs(像这样:"classpath:../services.xml"),也是不推荐的,因为运行时处理过程会选择"最近的"根路径然后引入他的父目录配置文件。Classpath配置的改变,会导致应用选择一个不同的、错误的目录。 你可以使用全路径限定资源定位取代相对路径,比如:"file:C:/config/services.xml" 或者"classpath:/config/services.xml"。还有,你可以使用抽象路径来解耦应用和配置文件。使用一个逻辑定位更可取 ,比如:通过"${..}"占位符,使用JVM运行时计算出的路径。

使用容器

ApplicationContext是一个高级工厂的接口,能维护各种bean以及他们之间依赖的注册。使用方法T getBean(String name, Class<T> requiredType),就能从定义的bean中获取实例。 ApplicationContext能让你读取bean定义、访问他们,如下:

// create and configure beans
ApplicationContext context =
new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});

// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// use configured instance
List<String> userList = service.getUsernameList();  

使用getBean()从beans中获取实例。ApplicationContext接口有几种方法可以办到,但是理想的做法是不要使用他们。实际上,应用中根本就不该使用getBean()方法,这样就不依赖Sprig API了。比如,Spring集成了很多web框架,为各种web框架类提供了依赖注入,比如web框架的Controller和JSF-managed beans

Bean概述

Spring IoC容器管理一个或多个bean。这些bean根据提供给容器的配置元数据创建的,比如使用XML格式<bean/>定义。
在容器内部,这些bean的定义用 BeanDefinition对象表示,BeanDefinition包含了下列元数据:

  • 全路径(含包路径)类名:代表bean的实际实现类。
  • Bean行为配置元素,它规定了bean在容器中行为(作用域,生命周期回调函数等等)
  • 引用其他bean,就是为了bean能正常工作而所需的其他bean的引用。这些引用类也称为合作类或者依赖类。
  • 其他配置,为实例设置的其他属性配置。比如说,管理连接池的bean的连接数,池大小的上限。

这些元数据将转换成bean定义(BeanDefinition类)的属性。

The bean definition
属性 :详情
class :Section 5.3.2, “Instantiating beans”
name :Section 5.3.1, “Naming beans”
scope:Section 5.5, “Bean scopes”
constructor arguments:Section 5.4.1, “Dependency injection”
properties:Section 5.4.1, “Dependency injection”
autowiring mode:Section 5.4.5, “Autowiring collaborators”
lazy-initialization mode:Section 5.4.4, “Lazy-initialized beans”
initialization method:the section called “Initialization callbacks”
destruction method:the section called “Destruction callbacks”

除了bean的信息以外,BeanDefinition也包含创建特殊bean的信息,ApplicationContext的实现也允许注册由用户创建而非IoC容器创建的对象。通过访问ApplicationContext’s BeanFactory的方法getBeanFactory(),该方法返回BeanFactory的实现DefaultListableBeanFactoryDefaultListableBeanFactory类支持这种注册,通过registerSingleton(..)registerBeanDefinition(..)方法实现。然而,典型的应用只用元数据定义的bean就可以单独运行。

beans命名

bean有一个或者多个标示符。这些标示符必须是所在容器范围内必唯一的。通常情况一下,一个bean仅有一个标示符,如果有需求需要多个,多出来的将被当做别名。

在XML格式配置元数据中,使用 id 或者 name 属性来作为bean的标示符。id属性只能有1个。命名规范是字符数字混编(myBean,fooService,等等),但也支持特殊字符,可以包含。若想给bean起个别名,则可使用name属性来指定,可以是多个,用英文的逗号(,)分隔、分号(;)也行、空格也行。注意,在Spring3.1以前,id属性定义成了xsd:ID类型,该类型强制为字符(译者心里说:估计字母+特殊字符,不支持数字的意思,有待验证,没工夫验证去了,翻译进度太慢了。再说了,现在都用4了,你再说3.0怎么着怎么着,那不跟孔乙己似的跟别人吹嘘茴香豆有四种写法)。3.1版开始,它被定义为xsd:string类型。注意,bean id的唯一性约束依然被容器强制使用,尽管xml解析器不再支持了。译者注:在spring3(含)以前,id是可以相同的,容器会替换相同id的bean,而在新版中,容器初始化过程中发现id相同抛出异常,停止实例化

id 和name属性不是bean所必须的。若未明确指定id或者name属性,容器会给它生成一个唯一name属性。当然了,如果你想通过bean的name属性引用,使用ref元素方式,或者是类似于Service Locator模式方式检索bean(译者想:应该是指调用ApplicationContext.getBean()方法获取bean,类似这种方式。Service Locator是一种设计模式,其实换个名字是不是更合适,DL(Dependency Lookup依赖查找)。虽然现在我也不明白,但是下面有专门的章节讲解,翻到时候再详细了解),就必须给bean指定 name了。之所以支持无name bean特性,是为了使内部类自动装配。

Bean命名规范

bean命名规范使用java命名规范中实例属性名(也称域,Field)规范。小写字母开头的驼峰式。像这样(不包括单引号)`accountManager`,`accountService`,`userDao`,
`loginController`,等等

规范的命名使配置易读易理解。若使用Spring AOP,通过名字增强(译注:大多数Spring AOP教材中的 通知)一坨bean时,规范的命名将带来极大的方便。
bean定义之外设置别名

定义的bean内,可以给bean多个标识符,组合id属性值和任意数量的name属性值。这些标识符均可作为该bean的别名,对于有些场景中,别名机制非常有用,比如应用中组件对自身的引用。(译注:一个类持有一个本类的实例作为属性,看起来应该是这样的,以下代码为推测,可以执行)
Bean类

public class SomeBean {
    //注意看这个属性,就是本类
    private SomeBean someBean;

    public SomeBean(){}

    public void setSomeBean(SomeBean someBean) {
        this.someBean = someBean;
    }
}

配置元数据

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--看bean的别名,使用,/;/空格 分隔都是可以是-->
    <bean id="someBeanId" name="someBean,someBeanA;someBeanB someBeanC" class="com.example.spring.bean.SomeBean">
        <!--将别名为someBeanA 的bean 注入给 id为someBeanId 的bean的属性 'someBean'-->
        <property name="someBean" ref="someBeanA"></property>
    </bean>
</beans>

测试代码

@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class SomeBeanTests {

    @Autowired
    @Qualifier("someBeanId")
    private SomeBean someBean;

    @Test
    public void testSimpleProperties() throws Exception {
    }

}

在bean的定义处指定所有别名有时候并不合适,然而,在其他配置文件中给bean设置别名却更为恰当。此法通常应用在大型系统的场景中,配置文件分散在各个子系统中,每个子系统都有本系统的bean定义。XML格式配置元数据,提供<alias/>元素,可以搞定此用法。

<alias name="fromName" alias="toName"/>

这种情况下,在同容器中有个叫fromName的bean,或者叫其他的阿猫阿狗之类的,再使用此别名定义之后,即可被当做toName来引用。

举个栗子,子系统A中的配置元数据也许引用了一个被命名为subsystemA-dataSource的bean。子系统B也许引用了一个subsystemB-dataSource。将这两个子系统整合到主应用中,而主应用使用了一个myApp-dataSource,为了使3个bean引用同一个对象,得在MyApp配置元数据中使用别名定义:

<alias name="subsystemA-dataSource" alias="subsystemB-dataSource"/>
<alias name="subsystemA-dataSource" alias="myApp-dataSource" />

现在,每个组件和主应用都能通过bean 名引用dataSource,而bean名都是唯一的保证不与其他定义冲突(实际上创建了一个命名空间),但他们引用的都是同一个bean。

Java-configuration

如果你使用了Java-configuration@Bean注解也提供了别名,详见Section 5.12.3, “Using the @Bean annotation”

bean实例化

bean的定义,本质是如何创建一个或多个对象的配方。容器被请求时,会解析配置元数据中的bean定义并封装,使用封装配置创建(或者获取)对象实例。

若使用XML格式配置元数据,得为将要实例化的对象指定类型(或者说是类),使用<bean/>元素的class属性实现。class属性 映射到BeanDefinition类实例的Class属性(域),这个class属性是<bean/>元素必须的。(例外情况,参看“Instantiation using an instance factory method” 和 Section 5.7, “Bean definition inheritance”。使用Class域属性,通过以下两种方式:

  • 通常,通过指定bean的class 属性,容器使用反射调用其构造函数直接创建bean,有点像Java 编码中使用new操作符。
  • 指定class实际类含有用于创建对象的静态工厂方法,这是不常使用的场景,容器会调用类的静态工厂方法创建bean。调用静态工厂方法返回的对象类型也许是相同类型,也许完全是其他类。
内部类命名 若要定义静态内部类,得将类名劈开。

举例来说,现在在com.example包有个类Foo,该类有静态内部类Bar,定义Bar的Spring bean的`class`属性差不多是这样

com.example.Foo$Bar 

注意$字符,用它来分隔内部类名和外围类名

用构造函数实例化

若是使用构造函数方式创建bean,所有的常规类都可以使用Spring来创建、管理。也就是说,开发的类无需实现任何特殊接口或者使用某种特殊编码风格。仅需指定bean的class即可。对于特殊的bean管理,取决于你使用的IoC类型,也许需要一个默认的空构造。

Spring IoC容器几乎能管理任何你需要管理的类,不局限于真正的JavaBeans。大多数Spring的用户心中,真正的JavaBean是这样的:仅有1个默认的无参构造函数、属性、setter、getter。嗯,比如,现在需要使用一个废弃连接池,它肯定不符合JavaBean规范,Spring照样能管理。

使用XML格式配置元数据 定义bean的class,如下所示:

<bean id="exampleBean" class="examples.ExampleBean"/>

<bean name="anotherExample" class="examples.ExampleBeanTwo"/>  

如何为构造函数指定参数?如何在对象实力话之后设置其属性?请参看Injecting Dependencies

使用静态工厂方法实例化 定义使用使用静态工厂方法创建的bean时,得指定工厂方法类的作为`class`属性值,并且还得指定工厂方法类中用于创建bean的方法名称,作为`factory-method`属性值。工厂方法可以有参数,调用该方法即可返回对象实例,就像通过构造函数创建对象实例一样。此种bean定义是为了兼容遗留系统中的静态工厂 下面的bean定义,是使用工厂方法创建bean的方式。定义中,无需指定返回对象的类型(class),而是指定工厂方法类的`class`。下例中,`createInstance()`方法必须是一个`static`静态方法。 
public class ClientService { private static ClientService clientService = new ClientService(); private ClientService() {} public static ClientService createInstance() { return clientService; } }
使用实例工厂方法实例化

静态工厂方法类似的还有实例工厂方法,使用实例工厂方法的方式实例化,是调用容器中已存在的bean的一个非静态方法来创建一个bean。用法是,1、class属性置空设置。 2、设置factory-bean属性,其值为当前容器(或者父容器)中bean的名字,该bean包含可供调用的创建对象的实例方法。3、设置factory-method属性,其值为工厂方法名。

<!-- 工厂类, 包含一个方法createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>

<!-- the bean to be created via the factory bean -->
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

工厂类如下

public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();
    private DefaultServiceLocator() {}

    public ClientService createClientServiceInstance() {
    return clientService;
    }
}

工厂类可以有多个工厂方法:

<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

<bean id="accountService"
    factory-bean="serviceLocator"
    factory-method="createAccountServiceInstance"/>

工厂类如下:

public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();
    private static AccountService accountService = new AccountServiceImpl();

    private DefaultServiceLocator() {}

    public ClientService createClientServiceInstance() {
        return clientService;
    }

    public AccountService createAccountServiceInstance() {
        return accountService;
    }

}

上例中展示了工厂类本身也可以通过 DI 管理和配置。参看DI详情

注意

Srping 资料中,factory bean是指一个Spring配置的bean,该bean能通过实例或者静态工厂方法创建对象。对比之下,FactoryBean(注意大写)是指Spring术语FactoryBean。这段没太理解,解释factory bean和FactoryBean

依赖

企业应用绝不会只有1个简单对象(或者说Spring bean)。哪怕是最简单的应用,也会包含许多对象协同工作。下一章节讲述,如何为真正的应用定义大量的、独立的bean,并让这些对象一起合作。

依赖注入

依赖注入(DI),是一个有对象定义依赖的手法,也就是,如何与其他对象合作,通过构造参数、工厂方法参数、或是在对象实例化之后设置对象属性,实例化既可以构造也可以是使用工厂方法。容器在它创建bean之后注入依赖。这个过程从根本上发生了反转,因此又名控制反转(Ioc),因为Spring bean自己控制依赖类的实例化或者定位 ,Spring bean中就有依赖类的定义,容器使用依赖类构造器创建依赖类实例,使用Service Locator模式`定位依赖类。

DI机制使代码简洁,对象提供它们的依赖,解耦更高效。对象无需自己查找依赖。同样的,类更容易测试,尤其当依赖接口或者抽象类时,测试允许在单元测试中使用stub或者mock(模拟技术)实现。

DI有2种主要方式,构造注入 和 setter注入
构造注入,容器调用构造函数并传参数,每个参数都是依赖。调用静态工厂方法并传参数方式构造bean和构造注入差不多,这里是指构造注入处理参数和静态工厂方法处理参数像类似。下例中展示了一个只能使用构造注入的类。注意,此类无任何特别之处,并未依赖容器指定的接口、基类、注解,就是一个POJO

public class SimpleMovieLister {

    // the SimpleMovieLister 依赖 a MovieFinder
    private MovieFinder movieFinder;

    //Spring容器能注入MovieFinder的构造函数
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // 实际如何使用MovieFinder的业务逻辑省略了

}
构造函数参数解决方案

构造参数解决方案,会匹配所使用的参数类型。如果在bean的定义中,构造参数不存在歧义,那么,在bean定义中定义的构造参数的次序,在bean实例化时,就是提供给适合的构造参数的次序。看这个类: package x.y;

public class Foo {

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

}

不存在歧义,假设BarBaz类没有集成关系,那么下面的配置是合法的,而且,不需要在<constructor-arg/>元素里指定构造参数的明确的indexes索引或者类型。

<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</true>,Srping不能决定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>

当构造函数有2个相同类型的参数,指定次序可以解决此种情况。注意index是从0开始

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

记住,若要使Spring能从构造函数查找参数名字,代码在编译时必须开启调试模式。若你没有开启调试模式(或者不想),可以使用@ConstructorProperties JDK 注解明确指定构造参数的name。样例程序:

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方法,bean是使用无参构造函数返回的实例,或者无参静态工厂方法返回的实例。 下面样例中展示了只能使用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注入。定义依赖,会转换成某种形式的BeanDefinition类,BeanDefinition类与PropertyEditor实例配合,即可将属性从一种格式转换成其他格式。然而,大多数程序员不会直接使用这些类(也就是编程式),更多的是使用XML、注解(也就是@Component@Controller等等),或者@Configuration注解的类中的方法上使用 @Bean。这些配置数据,都会在容器内部转换成BeanDefinition,用于加载整个Spring Ioc 容器。

构造注入对比setter注入

何时使用构造注入,何时使用setter注入,经验法则是:强制依赖用构造,可选依赖用Setter。注意,在settter方法上使用@Required注解即可另属性强制依赖。

Spring 团队建议,构造注入的实例是不可变的,不为null的。此外,构造注入组件要将完全初始化后的实例返回给客户端代码。还有,大量参数的构造函数是非常烂的,它意味着该类有大量的职责,得重构。

setter注入主要用于可选依赖,类内部可以指定默认依赖。否则类内所有使用依赖的地方,都得进行非空校验。setter注入的有个好处就是,类可以重配置或者再注入。因此,使用JMX MBeans进行管理的场景中,就非常适合setter注入。

使用何种依赖注入方式,对于某些类,非常有意义。有时协同第三方类处理,没有源码,由你来决定使用何种方式。比如,第三方类未暴露任何setter方法,那么构造注入也许就是唯一的可行的注入方式了。

依赖处理过程

容器解析bean依赖如下:

  • ApplicationContext创建后用配置元数据中描述的所有bean进行初始化。配置元数据格式可以是XML、Java Code,或者注解。
  • 每个bean的依赖,都会以下列形式表达:属性、构造参数,静态工厂方法的参数。当bean真正的创建时,这些依赖会被提供给bean。
  • 每个属性或者构造函数或者以value值形式在bean处直接设置,或者引用容器中其他bean。
  • 每一个属性或者构造参数都是一个值,该值将会从指定的格式转换为属性、构造参数的真正类型。Spring默认会将一个String类value转换成内建类型,比如int,long,String,boolean等等

Spring容器在创建bean之前会验证bean的配置。在bean创建之前,bean的属性不会赋值。当容器创建之后,会创建被设置为预先初始化的sington-scope单例作用域bean,非单例作用域bean,只有在请求时才会创建。作用域,在5.5章有定义,"Bean 作用域"。一个bean的创建,可能会引起许多bean的创建。因为bean的依赖以及依赖的依赖得先创建好用于引用。不涉及首先创建的bean及其依赖类bean,会稍后创建。

循环依赖 如果你主要使用构造注入,可能会创建一个循环依赖,该依赖不能解析。

举个栗子:类A需要类B的实例,使用了构造注入,类B需要一个类A的实例,也用了构造注入。若在配置文件中配置类A的bean和类B的bean互相注入,Spring IoC容器在运行时发现循环引用,抛出异常BeanCurrentlyInCreationException

一般使用Setter注入替代构造注入,这需要修改源码改配置,来解决循环依赖。避免使用构造注入或者只使用setter,都能避免循环依赖。 换句话说,虽然不推荐循环依赖,但是你可以使用setter注入来完成循环依赖。

和大多数场景(无循环引用)不一样的是,循环引用中的类A和类B中,得强制其中一个自己能完全初始化,然后注入给另一个(经典的先有鸡现有蛋的问题)。 循环依赖end

对于Spring,你经管放心,它非常智能。他能在容器加载期发现配置中的问题,比如:引用了一个不存在的bean、循环依赖。Spring在bean创建后,会尽可能迟的设置bean属性并处理依赖。这意味着,spring容器正确加载之后,当你请求一个对象而该对象的创建有问题或者是该对象的依赖有问题时,也能产生一个异常。举例来说,因为属性找不到,或者属性无效, 导致bean抛出异常。这可能会延迟发现配置问题,这就是为什么ApplicationContext默认会预先实例化单例bean。在这些bean被实际请求之前就创建,会消耗一些时间和内存,但是在ApplicationContext创建后你就能发现配置问题,而不是更迟。如果你愿意 ,也可以重写该行为,让单例bean延迟初始化。

如果没有循环依赖,当一个或者多个合作bean被注入到他们的依赖类时,每一个合作bean将会比依赖类更早的实例化。也就是说,如果bean A依赖bean B,Spring Ioc容器在调用A的setter方法之前,会先实例化B。换句话说,bean先实例化(非单例),然后设置依赖,然后调用相关声明周期方法(比如配置的init方法,或者是初始化回调函数)。

注入依赖样例

The following example uses XML-based configuration metadata for setter-based DI. A small part of a Spring XML configuration file specifies some bean definitions: 下面例子中使用了XML配置元数据,setter注入方式。XML 配置文件中的片段定义了bean:

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- 使用内嵌的ref元素完成setter注入 -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- 使用ref属性完成setter注入 -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

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

看java代码 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;
    }

}

In the preceding example, setters are declared to match against the properties specified in the XML file. The following example uses constructor-based DI: 上例中,setter方法名要和XML文件中的property元素的name属性相匹配。下面演示使用构造注入 :

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

看java代码

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

看java代码

public class ExampleBean {

    //私有构造函数
    private ExampleBean(...) {
        ...
    }

    // 静态工厂方法; 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元素产生,就像是bean的构造函数一样.工厂方法返回的类的类型无需和工厂类类型相同,虽然本例中他们是相同的。实例工厂方法(非静态)和静态工厂方法本质相同(除了使用facory-bean属性替代class属性,其他都相同),因此细节就不讨论了。

依赖和配置详解

前面章节提到的,你可以定义的bean的属性和构造参数引用其他的Spring bean(合作者),或者是使用value属性设置其值。Spring XML格式配置元数据至此<property/><constructor-arg/>子元素,用以实现构造注入和属性注入。

直接赋值(原始类型、String等等)

<property />元素的value属性为对象域属性或者构造参数设置了一个可读的字串。Spring的会将其转换为实际的与属性或者参数的数据类型。

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

下面的样例,是使用了XML配置中的p命名空间,他让XML更加简洁

<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
    http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close"
        p:driverClassName="com.mysql.jdbc.Driver"
        p:url="jdbc:mysql://localhost:3306/mydb"
        p:username="root"
        p:password="masterkaoli"/>

</beans>

The preceding XML is more succinct; however, typos are discovered at runtime rather than design time, unless you use an IDE such as IntelliJ IDEA or the Spring Tool Suite (STS) that support automatic property completion when you create bean definitions. Such IDE assistance is highly recommended. 上面的XML更简洁;然而,错别字,要在运行期才能发现而不能再开发期发现,除非你使用IDE支持自动补全。这样的的IDE的助手真心推荐。

也可以这样配java.unit.Properties实例:

<bean id="mappings"
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

    <!-- typed as a java.util.Properties -->
    <property name="properties">
        <value>
            jdbc.driver.className=com.mysql.jdbc.Driver
            jdbc.url=jdbc:mysql://localhost:3306/mydb
        </value>
    </property>
</bean>

Spring 容器通过JavaBean的PropertyEditor机制将<value/>元素内的值转换到java.util.Properties实例。这是非常棒的,Spring团队最喜欢的几处好用之处之一:用内嵌<value/>元素替代 值属性风格。

元素`idref`

idref元素用来将容器内其它bean的id传给<constructor-arg/> 或 <property/>元素,同时提供错误验证功能。

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

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

上面的bean定义在运行时等同于下面这一段定义:

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

<bean id="client" class="...">
    <property name="targetName" value="theTargetBean" />
</bean>

第一种格式比第二种要跟可取 ,因为使用idref标签,在开发期将允许容器校验引用bean真是存在。在第二个中,对于阿client bean是属性 targetName的值则没有校验执行 .client bean真正的实例化时,错别字才会被发现。如果clientbean是一个原型bean,这个错字导致的异常也许会等到部署到过后才能被发现。

注意

在4.0 beans xsd ,idref上的local属性不在支持。因为它不在支持正则表达bean引用 。当你升级到4.0的语法时,记得清除已经存在于idref元素上的local属性。

一个老生常谈的问题(至少是2.0以前了),<idref/>带来,在使用ProxyFactorybeanbean定义AOP拦截器时,当指定拦截器名字是使用<idref/>元素将校验拦截器id

猜你喜欢

转载自wwwiteye.iteye.com/blog/2184588