容器概览 (Container Overview)
org.springframework.context.ApplicationContext 接口代表了 Spring IoC 容器,它负责实例化、配置和组装 Bean。容器通过读取配置元数据 (Configuration Metadata) 来获取有关哪些组件需要实例化、配置和组装的指令。配置元数据可以用注解组件类、带有工厂方法的配置类、外部 XML 文件或 Groovy 脚本来表示。无论采用哪种格式,你都可以定义组成应用程序的组件以及这些组件之间丰富的相互依赖关系。
Spring 核心包中提供了 ApplicationContext 接口的多种实现。在独立应用程序中,通常会创建 AnnotationConfigApplicationContext 或 ClassPathXmlApplicationContext 的实例。
在大多数应用场景中,并不需要显式的用户代码来实例化一个或多个 Spring IoC 容器实例。例如,在普通的 Web 应用程序场景中,只需在应用程序的 web.xml 文件中编写简单的样板式 Web 描述符 XML 即可。在 Spring Boot 场景中,应用程序上下文会根据通用的设置约定为你隐式启动。
下图展示了 Spring 工作原理的高层视图。你的应用程序类与配置元数据相结合,在 ApplicationContext 被创建并初始化后,你就拥有了一个完全配置且可执行的系统或应用程序。
图 1. Spring IoC 容器
1. 配置元数据 (Configuration Metadata)
如上图所示,Spring IoC 容器消费一种形式的配置元数据。此配置元数据代表了你作为应用程序开发人员,如何通过配置告诉 Spring 容器在你的应用程序中实例化、配置和组装组件。
Spring IoC 容器本身与此配置元数据的实际编写格式是完全解耦的。如今,许多开发人员为他们的 Spring 应用程序选择 基于 Java 的配置 (Java-based configuration):
- 基于注解的配置 (Annotation-based configuration):在应用程序的组件类上使用注解来定义 Bean。
- 基于 Java 的配置 (Java-based configuration):通过使用 Java 配置类在应用程序类外部定义 Bean。要使用这些特性,请参考
@Configuration、@Bean、@Import和@DependsOn注解。
Spring 配置通常由容器必须管理的至少一个(通常是一个以上)Bean 定义组成。Java 配置通常在 @Configuration 类中使用标记了 @Bean 的方法,每个方法对应一个 Bean 定义。
这些 Bean 定义对应于组成应用程序的实际对象。通常,你会定义服务层对象、持久层对象(如 Repository 或数据访问对象 DAO)、表现层对象(如 Web 控制器)、基础设施对象(如 JPA EntityManagerFactory、JMS 队列等)。通常不会在容器中配置细粒度的领域对象,因为创建和加载领域对象通常是 Repository 和业务逻辑的责任。
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)
<!-- 这里放置此 bean 的协作对象和配置 -->
</bean>
<bean id="..." class="...">
<!-- 这里放置此 bean 的协作对象和配置 -->
</bean>
<!-- 这里放置更多 bean 定义 -->
</beans>id属性是一个字符串,用于标识单个 Bean 定义。class属性定义了 Bean 的类型,并使用完全限定类名。
id 属性的值可用于引用协作对象。此示例中未显示引用协作对象的 XML。有关更多信息,请参阅依赖注入 (Dependencies)。
对于实例化容器,需要将 XML 资源文件的位置路径提供给 ClassPathXmlApplicationContext 构造函数,让容器从本地文件系统、Java CLASSPATH 等各种外部资源加载配置元数据。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")注意:了解了 Spring IoC 容器后,你可能想了解更多关于 Spring 的
Resource抽象的内容。Resource路径用于构建应用程序上下文,详见 Application Contexts and Resource Paths。
以下示例展示了服务层对象 (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"/>
<!-- 这里放置此 bean 的其他协作对象和配置 -->
</bean>
<!-- 这里放置更多 service 的 bean 定义 -->
</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">
<!-- 这里放置此 bean 的其他协作对象和配置 -->
</bean>
<bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
<!-- 这里放置此 bean 的其他协作对象和配置 -->
</bean>
<!-- 这里放置更多数据访问对象的 bean 定义 -->
</beans>在上述示例中,服务层由 PetStoreServiceImpl 类和两个 JPA 类型的数据访问对象组成。property name 元素是指 JavaBean 属性的名称,而 ref 元素是指另一个 Bean 定义的名称。id 和 ref 元素之间的这种关联表达了协作对象之间的依赖关系。
组合基于 XML 的配置元数据
让 Bean 定义跨越多个 XML 文件非常有用。通常,每个单独的 XML 配置文件代表架构中的一个逻辑层或模块。
你可以使用 ClassPathXmlApplicationContext 构造函数从 XML 片段加载 Bean 定义。或者,使用一个或多个 <import/> 元素来从另一个或多个文件加载 Bean 定义:
<beans>
<import resource="services.xml"/>
<import resource="resources/messageSource.xml"/>
<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
</beans>在上述示例中,外部 Bean 定义从 services.xml 和 messageSource.xml 文件加载。所有位置路径都是相对于执行导入的定义文件的。虽然可以(但不推荐)使用相对路径 ../ 引用父目录中的文件,但这会创建对当前应用程序之外文件的依赖。
更好的做法是使用完全限定的资源位置(如 file:C:/config/services.xml)或通过 ${...} 占位符间接引用绝对路径。
2. 使用容器 (Using the Container)
ApplicationContext 是一个高级工厂接口,能够维护不同 Bean 及其依赖关系的注册表。通过方法 T getBean(String name, Class<T> requiredType),你可以检索 Bean 的实例。
ApplicationContext 允许你读取 Bean 定义并访问它们,如下例所示:
// 创建并配置 beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// 检索配置好的实例
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// 使用配置好的实例
List<String> userList = service.getUsernameList();import org.springframework.beans.factory.getBean
// 创建并配置 beans
val context = ClassPathXmlApplicationContext("services.xml", "daos.xml")
// 检索配置好的实例
val service = context.getBean<PetStoreService>("petStore")
// 使用配置好的实例
var userList = service.getUsernameList()最灵活的变体是 GenericApplicationContext 与读取器委托(Reader Delegates)相结合,例如:
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()最后,尽管 ApplicationContext 提供了 getBean 方法,但在理想情况下,你的应用程序代码根本不应该调用它。通过这种方式,你的代码对 Spring API 就没有任何依赖。例如,Spring 与 Web 框架的集成通过注解(如 @Autowired)提供了对控制器等组件的依赖注入。
补充教学 —— 容器的“魔法”与现实
1. 为什么会有多种 ApplicationContext 实现? Spring 的设计精髓之一是“灵活”。
- 如果你喜欢 XML 老派风格,用
ClassPathXmlApplicationContext。 - 如果你追求全注解、零配置,用
AnnotationConfigApplicationContext。 - 如果你是在开发 Web 应用,Spring Boot 会帮你自动选好
WebApplicationContext的变体。 核心点:无论你选哪种“外壳”,其内部处理 Bean 的核心逻辑都是一致的。
2. 配置文件 vs 注解:你应该选哪个?
- XML (曾经的王者):优点是解耦彻底,配置集中,不需要修改代码就能调整对象关系;缺点是繁琐、容易敲错、无法编译检查。
- 注解 (现代的主流):优点是开发效率极高,所见即所得;缺点是配置分散在各个类中,如果想全局查看对象依赖图比较困难。
- 最佳实践:业务代码(Service, Controller)用注解;第三方库(数据源、连接池)或需要动态配置的部分用
@Configuration类。
3. getBean() 的禁忌 正如文档最后提到的,少用甚至不用 getBean()。
- 如果你在代码里随处可见
context.getBean("xxx"),这其实是回到了“服务定位器”模式,违背了 IoC 的初衷。 - 正确姿势:声明一个成员变量,加上
@Autowired,让 Spring 主动塞给你。
4. 理解 “Domain Object” 不进容器 文档提到“细粒度的领域对象不配置在容器中”。
- 什么是领域对象? 比如
User类。一个系统中可能会产生几百万个User实例。 - 为什么不进容器? Spring Bean 通常是单例且无状态的服务(如
UserService)。如果把每一个User对象都让 Spring 管理,会造成巨大的内存压力,且并没有实际意义。容器是用来装“齿轮”的,而不是装“零件”的。