# 1.2 IoC容器概述

org.springframework.context.ApplicationContext接口代表着Spring IOC容器，它负责实例化、配置和组装bean。容器通过读取配置元数据获取关于要实例化、配置和组装的对象的指令。配置元数据以XML、Java注解或Java代码来表示。它定义了组成应用程序的对象以及这些对象之间的丰富依赖关系。

Spring提供了ApplicationContext接口的几个实现。在独立应用程序中，通常创建[ClassPathXMLApplicationContext](https://docs.spring.io/spring-framework/docs/5.1.8.RELEASE/javadoc-api/org/springframework/context/support/ClassPathXmlApplicationContext.html)或[FileSystemXMLApplicationContext](https://docs.spring.io/spring-framework/docs/5.1.8.RELEASE/javadoc-api/org/springframework/context/support/FileSystemXmlApplicationContext.html)的实例。虽然XML是定义配置元数据的传统格式，但你可以通过使用Java注解或代码作为元数据格式，并外加少量的XML配置，来声明对这些附加元数据格式的支持。

在大多数应用程序场景中，不需要显式代码来实例化SpringIOC容器的一个或多个实例。例如，在Web应用程序场景中，简单的在web.xml中用八行（或更多）标准Web描述符即可。如果你使用[Spring工具套件](https://spring.io/tools/sts)（一个支持Eclipse的开发环境），只需点击几下鼠标或按键，就可以轻松地创建这个标准配置。 下图显示了Spring如何工作的高级视图。应用程序类与配置元数据结合在一起，这样，在创建和初始化ApplicationContext之后，你就拥有了一个完全配置且可执行的系统或应用程序。

![图1.Spring IOC container](https://docs.spring.io/spring/docs/5.1.8.RELEASE/spring-framework-reference/images/container-magic.png)

## 1.2.1 配置元数据

如上图所示，SpringIOC容器使用一种形式的配置元数据。此配置元数据告诉应用程序开发人员，如何通知Spring容器实例化、配置和组装应用程序中的对象。

配置元数据一般是以简单直观的XML格式提供的，XML也会在本章的大部分内容用来表示Spring IOC容器的关键概念和特性。

> 基于XML的元数据不是唯一的配置元数据的方式。SpringIOC容器与编写配置元数据的格式完全分离。现在，更多的开发人员选择基于Java的配置来编写Spring应用程序。

有关在Spring容器中使用其他形式的元数据的信息，请参见：

* 基于注解的配置：Spring2.5引入了对基于注解的配置元数据的支持。
* 基于Java的配置：从Spring 3开始，Spring JavaConfig项目提供的许多特性成为核心Spring框架的一部分。因此，可以使用Java而不是XML文件来定义应用程序的bean。要使用这些新功能，请参见[@Configuration](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html)、[@Bean](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html)、[@Import](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Import.html)和[@DependsOn](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/DependsOn.html)注解。

Spring配置由容器必须管理的至少一个或多个bean定义组成。基于XML的配置元数据将这些bean配置为顶级< beans/>元素中的< bean/>元素。Java配置通常在@Configuration中使用@Bean注解的方法。

这些bean定义对应于组成应用程序的实际对象。一般用来定义服务层对象、数据访问对象（DAO）、表示对象（如Struts操作实例）、基础结构对象（如Hibernate会话行为）、JMS队列等。通常，我们不会在容器中配置细粒度的域对象，因为DAO和业务逻辑通常负责创建和加载域对象。但是，你可以使用Spring与AspectJ的集成来配置在IOC容器控制之外创建的对象。

下面的示例显示了基于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
        https://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属性的值是指协作对象。此例中没有显示用于引用协作对象的XML。有关详细信息，请参考依赖。

## 1.2.2. 实例化一个容器

容器的构造函数可以接收各种外部资源的配置元数据来实例化一个容器，例如本地文件系统、Java类路径等。

```
ApplicationContext context = new ClassPathXmlApplicationContext("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
        https://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
        https://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对象关系映射标准）的数据访问对象组成。property name元素引用了JavaBean的名字，ref元素引用另一个bean定义的名称。id和ref元素之间的链接表示协作对象之间的依赖关系。

**构建基于XML的配置元数据**

让一个文件中定义的bean可以在多个XML文件使用是非常有用的。通常，架构中的逻辑层或模块都可以用单独的XML配置文件来表示。 你可以使用应用程序上下文构造函数从这些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。所有位置路径都与执行导入的定义文件相关，因此services.xml必须与执行导入的文件位于同一目录或类路径位置，而messagesource.xml和themesource.xml必须位于导入文件位置下方的资源位置。如你所见，messageSource.xml的前导斜杠被忽略了。但是，因为我们使用的是相对路径，所以最好不要使用斜线。

根据Spring Schema的定义，要导入的文件的内容（包括顶层的< beans/>元素）必须是有效的XML bean。

> 可以（但不推荐）使用相对的“..”路径引用父目录中的文件。这样做会创建对当前应用程序外部文件的依赖关系。特别的，不建议使用这样的引用classpath:urls（例如classpath:../services.xml），因为程序在运行中解析时，将优先选择“最近”的classpath根目录，然后才会查看其父目录。当类路径配置发送变化时可能导致选择到不正确的目录。
>
> 你可以使用完全限定的资源位置，而不是相对路径：例如，文件：c:/config/services.xml或classpath:/config/services.xml。但是这样会将你的应用程序的配置耦合到特定的绝对位置。通常情况下，最好通过在运行时根据JVM系统属性解析的“$…”占位符，来为此类绝对位置保留间接寻址。

命名空间本身提供了导入指令功能。在Spring提供的一系列XML名称空间中，除了纯bean定义之外，还提供了其他配置功能，例如Context和Util名称空间。

**groovy bean定义DSL**

作为外部配置元数据的另一个例子，bean也可以用Spring的Groovy Bean定义DSL来表示，这在grails框架中是很常见的。通常，这种配置存在一个“.groovy”文件中，其结构如以下示例所示：

```
beans {
    dataSource(BasicDataSource) {
        driverClassName = "org.hsqldb.jdbcDriver"
        url = "jdbc:hsqldb:mem:grailsDB"
        username = "sa"
        password = ""
        settings = [mynew:"setting"]
    }
    sessionFactory(SessionFactory) {
        dataSource = dataSource
    }
    myService(MyService) {
        nestedBean = { AnotherBean bean ->
            dataSource = dataSource
        }
    }
}
```

这种配置风格在很大程度上相当于XML bean定义，甚至支持Spring的XML配置名称空间。它还允许通过import beans指令导入XML bean定义文件。

## 1.2.3. 使用容器

ApplicationContext是高级工厂的接口，它能够维护不同bean的注册及其依赖。通过使用方法`T getBean(String name, Class<T> requiredType)`，获取到bean的实例。 ApplicationContext允许你读取bean定义并访问它们，如下例所示：

```
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

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

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

使用Groovy配置，跟XML看起来非常相似。它有一个不同的上下文实现类，它支持Groovy（但也理解XML bean定义）。下面的示例显示了groovy配置：

```
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
```

最灵活的是GenericApplicationContext与reader delegates结合使用，例如使用XmlBeanDefinitionReader 来加载XML文件，如下示例所示：

```
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
```

你也可以使用GroovyBeanDefinitionReader来加载Groovy文件，如下所示：

```
GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();
```

你可以在同一ApplicationContext中混合使用这样的Reader，从不同的配置源读取bean定义。

然后可以使用getBean来获取到bean的实例。ApplicationContext接口还有一些其他的方法来获取bean，但理想情况下，你的应用程序代码不应该使用它们。实际上，你的应用程序代码根本不应该调用getBean（）方法，因此完全不依赖于SpringAPI。例如，Spring与Web框架的集成为各种Web框架组件（如控制器和JSF管理的bean）提供了依赖注入，允许你通过元数据（如自动注解方式）声明对特定bean的依赖。
