首页 > 代码库 > Spring核心技术(一)——IoC容器和Bean简介

Spring核心技术(一)——IoC容器和Bean简介

IoC容器和Bean简介

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

org.springframework.beansorg.springframework.context包是Spring框架IoC容器的基础。BeanFactory接口提供了一种先进的配置机制能够管理任何类型的对象。ApplicationContextBeanFactory的子接口。它增加了一些跟Spring AOP特性更为简单的集成,包括信息资源处理(国际化使用),事件发表,以及应用层特别上下文的使用。

简而言之,BeanFactory提供了框架配置和基本的功能,而ApplicationContext提供了更多企业级特性。ApplicationContextBeanFactory的超集,而且在这章Spring IoC容器中唯一使用的。想要更多的了解BeanFactory的话,请参考6.16。

在Spring中,那些在你应用中,由Spring IoC容器管理的骨干对象,都叫做Bean。Bean就是一个由Spring IoC容器实例化,装载,以及管理的对象。Bean也是你应用中的对象。Bean以及Bean的那些依赖对象,都是通过容器使用的元数据反射成的。

容器概览

接口org.springframework.context.ApplicationContext表示Spring IoC容器同时负责实例化,配置,以及装载前面提及的Bean对象。容器通过读取配置元数据来知道那些对象需要实例化,配置以及装载。配置元数据可以写到XML中,Java注解中,或者Java代码中。

几种不同的由Spring针对ApplicationContext接口的实现都是可以直接使用的。在单机环境中,使用ClassPathXmlApplicationContext或者FileSystemXmlApplicationContext也是非常常见的。尽管XML是传统的定义元数据的格式,你也可以通过Java注解或者代码来提供额外的元数据。

在大多数应用场景中,用户代码不需要实例化Spring IoC容器。比如,在Web应用场景下,只需要在web.xml中添加少数几行代码就可以由Web容器来创建Spring IoC容器。

下面的图是一个high-level的Spring工作图。你的应用的类以及配置当中的元数据只有在ApplicationContext创建了,初始化好,你才有一个完全配置好的,可执行的系统或应用。

技术分享

图6.1 Spring IoC 容器

配置元数据

如前面的图所表现的,Spring IoC容器会使用配置元数据。这个数据也表示了你希望Spring容器在应用中如何来实例化,配置,以及装载对象。

配置元数据传统的提供方式是使用简单直观的XML格式,当然也是本章所用来表达Spring IoC容器的一些关键的概念和特性的格式。

基于XML格式的元数据配置不是唯一的配置元数据的方式。Spring IoC容器本身和使用哪一种元数据来写入配置是完全解耦的。目前很多开发者也选择使用基于Java的方式来配置Spring应用。

关于使用其他不同形式的元数据,可以参考

  • 基于注解的配置: Spring 2.5 支持基于注解的元数据配置
  • 基于Java的配置: Spring 3.0 以后,Spring JavaConfig项目成为了Spring 框架的一部分。 开发者可以通过定义Java类来定义Bean。如果想使用这些新特性,参考@Configuration,@Bean,@Import以及@DependsOn注解。

Spring 配置包括至少一种Bean的定义方式。基于XML配置元数据都是通过配置< bean/>这样的标签,在最高级别的< beans/>标签之下。也可以通过Java 配置使用 @Bean注解的方法到使用@Configuration注解的类上面。

这些Bean定义所关联的实际的对象构成了你的应用。通常,你可以定义服务层对象,数据接入层对象(Dao),表现层对象比如Struts里面的Action实例,基础构成对象比如Hibernate的SessionFactories,JMSQueues等等。通常不配置细粒度的域对象的容器,因为它通常是DAOs的责任和业务逻辑创建和加载域对象。然而,你可以使用Spring和AspectJ集成来配置在IoC容器控制之外的对象。可以参考文章 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="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->

</beans>

id属性是一个字符串,是用来区分独立的Bean定义的。class属性定义了Bean使用的类型,用的是全名。id的值用来让Bean对象之间相互引用。

实例化容器

实例化Spring IoC容器很直接。ApplicationContext的构造函数可以通过一些资源地址的字符串来让容器从中加载配置元数据。这些文件可以来自本地文件系统,或者Java的CLASSPATH等。

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

如下的例子展示了一个服务层对象(services.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">

    <!-- services -->

    <bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
        <property name="accountDao" ref="accountDao"/>
        <property name="itemDao" ref="itemDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for services go here -->

</beans>

如下的例子展示了一个数据接入层对象(daos.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>

在之前的例子中,服务层对象包括了类PetStoreServiceImpl,并且两个数据接入对象类型分别是JpaAccountDao以及JpaItemDao(基于JPA O/R mapping 标准)。property name 元素指的是Bean属性的名字,ref 指的是另一个定义的bean。这种id和ref元素的关联,表示了不同对象之间的依赖关系。

组合基于XML的元数据配置

有时将bean定义到多个XML文件更清晰一些。通常来说,在开发者的架构中一个单独的XML配置文件代表一个单独的逻辑层,或者单独的模块。

开发者可以使用应用上下文的构造函数来加载这些包含bean的XML。这个构造函数可以使用多个资源路径,比如之前一节中展示的那样。或者可以使用一个或者多个< 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定义通过3个文件:services.xml,messageSource.xml以及themeSource.xml来加载。所有路径都是相对于当前文件的所在的路径。所以services.xml必须和当前文件在同一个路径或classpath路径。而messageSource.xml和themeSource.xml必须定义在resources路径下。如上所述,第一个斜线是被忽视掉的,考虑到这些路径都是相对的,最好不要使用第一个下线。这些文件的内容是被引用的,包括最高级的< beans/>元素,所以这些文件针对Spring Bean的XML定义必须有效。

很可能,通过使用相对路径”../”来获得引用的文件,但是并不推荐这样做。这样做会创建一个针对当前应用的外部依赖。尤其是这个路径中包含“classpath”,注入这样的URL(比如,“classpath:../services.xml”),这样会引用一个运行时的classpath根目录,然后在查找其父目录。Classpath配置的改变可能会变成一个完全不一样的目录。
当然,你也可以使用一些完整的路径而不使用相对路径,比如像“file:C:/config/service.xml”或者“classpath:/config/services.xml”。然而,一定要注意这样做你是在耦合你的应用到你本地的绝对路径上。通常,更好的方式是使用引用来针对这些绝对路径,比如“${…}”这类占位符,JVM系统是可以在运行时解析的。

使用容器

ApplicationContext是一个负责注册不同Bean的工厂接口。可以通过T getBean(String name, Class<T> requiredType)方法获取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()来获得Bean对象。ApplicationContext接口有几个方法来获取Bean实例,但是开发者的代码中可能用不到这些方法。事实上,开发者的应用不该调用getBean()方法,且不依赖于Spring的API。比如,Spring与Web框架的集成,为多种框架的控制层等提供了依赖注入。

Bean 概述

Spring IoC容器管理了很多的Bean对象。这些Bean对象都是根据容器的配置元数据所创建的,比如基于XML的<bean/>定义。
在容器里面,Bean的定义都被表现为BeanDefinition对象,包含以下元数据:

  • package-qualified类名,通常就是实际实现Bean接口的类。
  • Bean的行为配置元素,也就是那些Bean在容器里应有的状态(范围,生命周期回调等等)
  • 引用到的其他Bean所必须的一些那些Bean配置。这些引用也称为依赖
  • 其他用来创建对象的一些配置,比如,Bean中引用用来管理连接池的连接数字,或者连接池的上限等。

除了使用实现定义的元数据来创建Bean,ApplicationContext的实现也允许开发者将已存在的容器外的对象注册为Bean对象。可以通过进去ApplicationContext的BeanFactory中的getBeanFactory()方法来获得在DefaultListableBeanFactory中实现的BeanFactory。DefaultListableBeanFactory通过registerSingleton(..)以及registerBeanDefinition(..)支持前面的操作。然而,通常情况下,应用都只是使用元数据中定义的Bean对象。

Bean的元数据以及手工支持的单例的最好尽早注册到Spring容器中,防止容器在装载这些Bean的过程中发生错误。然而,覆盖掉已存在的元数据和存在的单例Bean也是支持的,但是在运行时注册Bean有在官方上并不支持,而且因为Bean状态的不一致导致并发异常。

命名Bean

每一个Bean都有不止一个区分符。这些区分符必须在这个容器中唯一。通常,一个Bean只有一个区分符,但是如果多余一个,那么额外的区分符也作为这个Bean的别名。

在基于XML配置的元数据中,你可以使用id或者name属性来作为Bean的区分符。id属性允许你特指唯一的一个id。方便起见,这个名字都是有字符跟数字的(‘myBean’, ‘fooService’等),也可以包含特殊的字符。如果你也通过其他别名来使用Bean,开发者也可以给Bean使用name属性,以,,;或者空格来区分。由于历史的原因,在Spring 3.1之前,id属性是被定义成一种xsd:ID类型的。在3.1中,id的类型还被定义成xsd:string类型。

开发者也可以不给Bean定义id或者name。如果Bean没有名字或者id的话,容器会帮助Bean生成一个独特的名字。但是如果你想使用ref这样的元素来定位到Bean的话,你还是需要添加一个名字的。不使用名字主要是为了使用内在的Bean以及联合装载。

Bean的命名习惯
一般习惯就是根据Java的field变量的方式来命名。以小写开始的驼峰命名。比如accountManager,userDao,loginController等。
一致的命名方式可以让开发者配置更加简单易懂,而且如果你使用Spring AOP的话,这样做也很有益处。

在Bean定义之外增加别名

在Bean定义本身,开发者通过使用id属性以及name属性可以为Bean定义多个名字。这些名字也同样能指向相同的Bean对象,在一些场景下是很实用的。比如允许组件引用多个依赖的话,通过名字会更有效。

然而,在Bean定义的时候来特指别名有的时候是不够的。有的时候引用别名来定义在其他的地方能够更清晰。这也是大系统的一些常见场景,根据不同的子系统来区分配置信息,每个子系统都有自己的定义。在基于XML的配置元数据中,可以使用<alias/>元素来做到。

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

这种情况下,在仙童的容器中,所有name是fromName的Bean,也能通过alias定义来通过toName来引用。
举例来说,子系统A的元数据可能会通过一个subsystemA-dataSource来引用其数据源。而子系统B的配置元数据可能通过subsystemB-dataSource来引用数据源。当组合成一个应用时,会同时使用这两个子系统通过myApp-dataSource来引用数据源。如果希望通过3个名字来指向一个对象,你可以通过应用的配置元数据配置如下定义。

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

现在每个组件和主应用程序可以通过名称引用数据源是独一无二的,保证不与其他任何冲突定义(有效地创建一个名称空间),然而他们引用同一个bean。

实例化Bean

Bean定义的本身其实也是创建Bean对象的菜谱,容器通过这个定义来的元数据来将创建实际的Bean对象。

如果开发者使用的是基于XML的配置元数据,开发者可以通过特指Bean的class字段来确定Bean被实例化成指定的对象。class属性通常来说,在Bean定义中是必须的。开发者可以通过如下方式使用Class属性:

  • 通常,指定Bean的class属性,可以让容器直接通过Bean定义的类的够早函数来直接构成,某种程度上来说,也就是调用Java的new操作符。
  • 也可以特指某个静态的工厂方法来创建对象,当然只有少数情况需要由容器调用静态的工厂方法来创建Bean对象。静态工厂方法所返回的对象类型可是就是这个类本身,也可以是其他的类。

内部类。如果开发者想通过配置一个Bean为静态内部类,开发者需要指定二进制的嵌套类的类名。
举例来说,比如有一个类名为Foo在包com.example包之中,而且这个Foo类其中有一个静态的嵌套类叫做Bar,那么如果想使用Bar来作为Bean的话,它的class属性需要为
com.example.Foo$Bar
需要注意的是,$符号就是用来区外部类和内部类的。

通过构造函数实例化
当开发者通过构造函数来创建Bean对象的时候,所有的普通的类都能够和Spring协同工作。也就是说,一般的作为Bean的类是不需要实现一些特殊的接口的。仅仅指定Bean的类就足够了。然而,根据你使用IoC容器的不同,开发者可能需要配置默认(无参)构造函数。

Spring IoC容器可以帮你管理任何你想要管理的类。并不仅限于Bean对象。大多数的Spring开发者更多在容器中使用Bean对象配合getter,setter方法以及无参的构造函数。当然,开发者也可以在容器中管理一些非Bean样式的对象。比如说,一个不被引用的连接池,Spring仍然可以管理它。

使用基于XML的元数据配置方式,Bean的配置可以如下:

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

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

通过静态工厂方法实例化
当通过静态工厂方法来定义Bean对象的时候,开发者可以使用class属性来指定包含工厂方法的类,通过factory-method来指定生成Bean的方法。开发者可以调用这个方法,并返回一个对象。

下面的Bean定义,就是通过调用工厂方法所创建的。定义不会指定方法返回的对象的类型,而是包含了工厂方法的类。在如下的例子中createInstance()方法必须为静态方法。

<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>
public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}

通过实例工厂方法实例化
比较类似前面提到的静态工厂方法,不同的是,这次是通过调用非静态的实例方法来创建一个新的Bean对象的。如果想使用这种机制,需要将Bean的class属性置空,而是用factory-bean属性,特指容器中包含的那个包含创建该Bean实例方法的那个Bean。同时将这个Bean的factory-method属性为实际的调用方法。

<!-- the factory bean, which contains a method called 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;
    }

}

这个方法显示,工厂Bean本身也是可以通过依赖注入配置的。

在Spring文档中,工厂Bean指的是在Spring容器中配置的专门通过实例方法或者静态方法来创建Bean的一个Bean。相对而言,FactoryBean指的是Spring一种特指的FactoryBean.

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    Spring核心技术(一)——IoC容器和Bean简介