容器概述

The org.springframework.context.ApplicationContext 接口代表 Spring IoC 容器,负责实例化、配置和组装 bean。容器通过读取配置元数据来获取实例化、配置和组装组件的指令。配置元数据可以表示为带注解的组件类、带有工厂方法的配置类,或者外部 XML 文件或 Groovy 脚本。无论采用哪种格式,你都可以构建你的应用以及这些组件之间丰富的相互依赖关系。

ApplicationContext 接口的几种实现在 Spring 核心中。在独立应用中,通常创建 AnnotationConfigApplicationContextClassPathXmlApplicationContext 的实例。

在大多数应用场景中,不需要显式用户代码来实例化一个或多个 Spring IoC 容器实例。例如,在普通 Web 应用场景中,应用 web.xml 文件中的简单样板 Web 描述符 XML 就足够了(参阅 Web 应用的便捷 ApplicationContext 实例化)。在 Spring Boot 场景中,应用上下文会根据常见的设置约定为你隐式启动。

下图展示了 Spring 的高级工作视图。您的应用类与配置元数据相结合,这样在创建和初始化 ApplicationContext 后,您将拥有一个完全配置好的可执行系统或应用。

container magic
图 1. Spring IoC 容器

配置元数据

如前图所示,Spring IoC 容器消费某种形式的配置元数据。这些配置元数据代表了您作为应用开发者如何告诉 Spring 容器实例化、配置和组装应用中的组件。

Spring IoC 容器本身与配置元数据实际编写的格式完全解耦。如今,许多开发者为他们的 Spring 应用选择 基于 Java 的配置

Spring 配置至少包含一个(通常不止一个)由容器管理的 bean 定义。Java 配置通常在 @Configuration 类中使用 @Bean 注解的方法,每个方法对应一个 bean 定义。

这些 bean 定义对应于构成您应用的实际对象。通常,您会定义服务层对象、持久层对象(例如仓库或数据访问对象 (DAO))、表示层对象(例如 Web 控制器)、基础设施对象(例如 JPA EntityManagerFactory、JMS 队列等)。通常,容器中不配置细粒度的领域对象,因为创建和加载领域对象通常是仓库和业务逻辑的责任。

XML 作为外部配置 DSL

基于 XML 的配置元数据将这些 bean 配置为顶级 <beans/> 元素内的 <bean/> 元素。以下示例展示了基于 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="..."> (1) (2)
		<!-- 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>
1 id 属性是一个字符串,用于标识单个 bean 定义。
2 class 属性定义了 bean 的类型,使用完全限定类名。

id 属性的值可用于引用协作对象。本示例未展示引用协作对象的 XML。有关更多信息,请参阅依赖

为了实例化容器,需要向 ClassPathXmlApplicationContext 构造函数提供 XML 资源文件的位置路径或多个路径,该构造函数允许容器从各种外部资源(例如本地文件系统、Java CLASSPATH 等)加载配置元数据。

  • Java

  • Kotlin

ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")

学习 Spring 的 IoC 容器后,您可能想了解更多关于 Spring 的 Resource 抽象(如资源中所述)的信息,它提供了一种方便的机制,可以从 URI 语法定义的位置读取 InputStream。特别是,Resource 路径用于构建应用上下文,如应用上下文和资源路径中所述。

以下示例展示了服务层对象 (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 类和两个数据访问对象组成,类型分别为 JpaAccountDaoJpaItemDao(基于 JPA 对象关系映射标准)。property name 元素引用 JavaBean 属性的名称,ref 元素引用另一个 bean 定义的名称。idref 元素之间的这种关联表达了协作对象之间的依赖关系。有关配置对象依赖项的详细信息,请参阅依赖

组合基于 XML 的配置元数据

将 bean 定义分布在多个 XML 文件中会很有用。通常,每个单独的 XML 配置文件代表架构中的一个逻辑层或模块。

您可以使用 ClassPathXmlApplicationContext 构造函数从 XML 片段加载 bean 定义。这个构造函数接受多个 Resource 位置,如上一节所示。或者,使用一个或多个 <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.xmlmessageSource.xmlthemeSource.xml。所有位置路径都相对于进行导入的定义文件,因此 services.xml 必须与进行导入的文件位于同一目录或类路径位置,而 messageSource.xmlthemeSource.xml 必须位于导入文件位置下的 resources 位置。正如您所见,开头的斜杠会被忽略。但是,考虑到这些路径是相对的,最好根本不使用斜杠。被导入文件的内容(包括顶级 <beans/> 元素)必须根据 Spring Schema 是有效的 XML bean 定义。

可以使用相对路径 "../" 引用父目录中的文件,但不推荐这样做。这样做会创建对当前应用外部文件的依赖。特别是,对于 classpath: URL(例如,classpath:../services.xml),不推荐使用这种引用,因为运行时解析过程会选择“最近的”类路径根目录,然后查找其父目录。类路径配置更改可能导致选择不同且不正确的目录。

您始终可以使用完全限定的资源位置而不是相对路径:例如,file:C:/config/services.xmlclasspath:/config/services.xml。但是,请注意,这样做会将您的应用配置与特定的绝对位置耦合。通常最好保持对这些绝对位置的间接引用 — 例如,通过在运行时针对 JVM 系统属性解析的 "${…​}" 占位符。

命名空间本身提供了导入指令功能。Spring 提供的一系列 XML 命名空间中提供了超出普通 bean 定义的更多配置特性 — 例如,contextutil 命名空间。

Groovy Bean 定义 DSL

作为外部化配置元数据的另一个示例,bean 定义也可以使用 Spring 的 Groovy Bean Definition 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 配置命名空间。它还允许通过 importBeans 指令导入 XML bean 定义文件。

使用容器

The ApplicationContext 是一个高级工厂接口,能够维护不同 bean 及其依赖项的注册表。通过使用方法 T getBean(String name, Class<T> requiredType),您可以检索 bean 的实例。

The ApplicationContext 允许您读取 bean 定义并访问它们,如下例所示

  • Java

  • Kotlin

// 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();
import org.springframework.beans.factory.getBean

// create and configure beans
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")

// retrieve configured instance
val service = context.getBean<PetStoreService>("petStore")

// use configured instance
var userList = service.getUsernameList()

使用 Groovy 配置,启动过程看起来非常相似。它有一个不同的上下文实现类,该类感知 Groovy(但也理解 XML bean 定义)。以下示例展示了 Groovy 配置

  • Java

  • Kotlin

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

最灵活的变体是 GenericApplicationContext 结合读者委托(reader delegates)— 例如,使用 XmlBeanDefinitionReader 处理 XML 文件,如下例所示

  • Java

  • Kotlin

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

您也可以对 Groovy 文件使用 GroovyBeanDefinitionReader,如下例所示

  • Java

  • Kotlin

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

您可以在同一个 ApplicationContext 上混合和匹配这些读者委托,从不同的配置源读取 bean 定义。

然后,您可以使用 getBean 来检索 bean 的实例。ApplicationContext 接口还有一些用于检索 bean 的其他方法,但理想情况下,您的应用代码永远不应使用它们。实际上,您的应用代码根本不应该调用 getBean() 方法,因此完全不依赖于 Spring API。例如,Spring 与 Web 框架的集成提供了对各种 Web 框架组件(如控制器和 JSF 管理的 bean)的依赖注入,允许您通过元数据(例如自动装配注解)声明对特定 bean 的依赖。