Skip to content

使用 AnnotationConfigApplicationContext 实例化 Spring 容器 (Instantiating the Spring Container by Using AnnotationConfigApplicationContext)

接下来的章节记录了在 Spring 3.0 中引入的 Spring AnnotationConfigApplicationContext。这是一个功能强大的 ApplicationContext 实现,它不仅能够接受 @Configuration 类作为输入,还能接受普通的 @Component 类以及使用 JSR-330 元数据标注的类。

当提供 @Configuration 类作为输入时,@Configuration 类本身会被注册为一个 Bean 定义,并且该类中所有声明的 @Bean 方法也都会被注册为 Bean 定义。

当提供 @Component 和 JSR-330 类时,它们会被注册为 Bean 定义,并且假设在必要时这些类内部使用了诸如 @Autowired@Inject 之类的 DI 元数据。

简单构造 (Simple Construction)

正如在实例化 ClassPathXmlApplicationContext 时使用 Spring XML 文件作为输入一样,你在实例化 AnnotationConfigApplicationContext 时可以使用 @Configuration 类作为输入。这允许你完全摆脱 XML 来使用 Spring 容器,如下例所示:

java
public static void main(String[] args) {
	ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
	MyService myService = ctx.getBean(MyService.class);
	myService.doStuff();
}
kotlin
import org.springframework.beans.factory.getBean

fun main() {
	val ctx = AnnotationConfigApplicationContext(AppConfig::class.java)
	val myService = ctx.getBean<MyService>()
	myService.doStuff()
}

如前所述,AnnotationConfigApplicationContext 不限于仅处理 @Configuration 类。任何标注了 @Component 或 JSR-330 注解的类都可以作为参数提供给构造函数,如下例所示:

java
public static void main(String[] args) {
	ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
	MyService myService = ctx.getBean(MyService.class);
	myService.doStuff();
}
kotlin
import org.springframework.beans.factory.getBean

fun main() {
	val ctx = AnnotationConfigApplicationContext(MyServiceImpl::class.java, Dependency1::class.java, Dependency2::class.java)
	val myService = ctx.getBean<MyService>()
	myService.doStuff()
}

前面的例子假设 MyServiceImplDependency1Dependency2 使用了 Spring 的依赖注入注解,例如 @Autowired

使用 register(Class<?>...​) 编程式构建容器

你可以使用无参构造函数实例化 AnnotationConfigApplicationContext,然后使用 register() 方法对其进行配置。这种方法在编程式构建 AnnotationConfigApplicationContext 时特别有用。以下示例展示了如何操作:

java
public static void main(String[] args) {
	AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
	ctx.register(AppConfig.class, OtherConfig.class);
	ctx.register(AdditionalConfig.class);
	ctx.refresh();
	MyService myService = ctx.getBean(MyService.class);
	myService.doStuff();
}
kotlin
import org.springframework.beans.factory.getBean

fun main() {
	val ctx = AnnotationConfigApplicationContext()
	ctx.register(AppConfig::class.java, OtherConfig::class.java)
	ctx.register(AdditionalConfig::class.java)
	ctx.refresh()
	val myService = ctx.getBean<MyService>()
	myService.doStuff()
}

使用 scan(String...​) 启用组件扫描 (Enabling Component Scanning)

要启用组件扫描,你可以按如下方式标注你的 @Configuration 类:

java
@Configuration
@ComponentScan(basePackages = "com.acme") // (1)
public class AppConfig  {
	// ...
}
kotlin
@Configuration
@ComponentScan(basePackages = ["com.acme"]) // (1)
class AppConfig  {
	// ...
}
  1. 此注解用于启用组件扫描。

提示

经验丰富的 Spring 用户可能熟悉 Spring context: 命名空间中等价的 XML 声明,如下例所示:

xml
<beans>
	<context:component-scan base-package="com.acme"/>
</beans>

在前面的例子中,将扫描 com.acme 包以查找任何标注了 @Component 的类,并将这些类注册为容器中的 Spring Bean 定义。AnnotationConfigApplicationContext 提供了 scan(String...) 方法,以实现相同的组件扫描功能,如下例所示:

java
public static void main(String[] args) {
	AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
	ctx.scan("com.acme");
	ctx.refresh();
	MyService myService = ctx.getBean(MyService.class);
}
kotlin
fun main() {
	val ctx = AnnotationConfigApplicationContext()
	ctx.scan("com.acme")
	ctx.refresh()
	val myService = ctx.getBean<MyService>()
}

::: note 注意 请记住,@Configuration 类被 元注解@Component,因此它们是组件扫描的候选对象。在前面的例子中,假设 AppConfig 声明在 com.acme 包(或其子包)内,它将在调用 scan() 时被检测到。在 refresh() 之后,它的所有 @Bean 方法将被处理并注册为容器内的 Bean 定义。 :::

对 Web 应用程序的支持:AnnotationConfigWebApplicationContext

AnnotationConfigApplicationContextWebApplicationContext 变体是 AnnotationConfigWebApplicationContext。在配置 Spring ContextLoaderListener Servlet 监听器、Spring MVC DispatcherServlet 等组件时,可以使用此实现。以下 web.xml 代码片段配置了一个典型的 Spring MVC Web 应用程序(注意 contextClass 上下文参数和初始化参数的使用):

xml
<web-app>
	<!-- 配置 ContextLoaderListener 以使用 AnnotationConfigWebApplicationContext
		而不是默认的 XmlWebApplicationContext -->
	<context-param>
		<param-name>contextClass</param-name>
		<param-value>
			org.springframework.web.context.support.AnnotationConfigWebApplicationContext
		</param-value>
	</context-param>

	<!-- 配置位置必须包含一个或多个以逗号或空格分隔的
		全限定 @Configuration 类。也可以指定全限定包进行组件扫描 -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>com.acme.AppConfig</param-value>
	</context-param>

	<!-- 像往常一样使用 ContextLoaderListener 引导根应用上下文 -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!-- 像往常一样声明 Spring MVC DispatcherServlet -->
	<servlet>
		<servlet-name>dispatcher</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<!-- 配置 DispatcherServlet 以使用 AnnotationConfigWebApplicationContext
			而不是默认的 XmlWebApplicationContext -->
		<init-param>
			<param-name>contextClass</param-name>
			<param-value>
				org.springframework.web.context.support.AnnotationConfigWebApplicationContext
			</param-value>
		</init-param>
		<!-- 同样,配置位置必须包含一个或多个以逗号或空格分隔的
			全限定 @Configuration 类 -->
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>com.acme.web.MvcConfig</param-value>
		</init-param>
	</servlet>

	<!-- 将所有 /app/* 请求映射到 dispatcher servlet -->
	<servlet-mapping>
		<servlet-name>dispatcher</servlet-name>
		<url-pattern>/app/*</url-pattern>
	</servlet-mapping>
</web-app>

::: note 注意 对于编程式的使用场景,可以使用 GenericWebApplicationContext 作为 AnnotationConfigWebApplicationContext 的替代方案。详情请参阅 GenericWebApplicationContext 的 Javadoc。 :::


补充教学 —— 深入理解 AnnotationConfigApplicationContext 的工作原理

作为现代 Spring 开发的基础设施,理解 AnnotationConfigApplicationContext 的底层逻辑能帮你避开很多“诡异”的坑。

1. 两大核心组件:读取器与扫描器AnnotationConfigApplicationContext 内部其实是由两个主要工具协作完成工作的:

  • AnnotatedBeanDefinitionReader:负责将你传入的 Class 解析成 BeanDefinition。它不仅处理 @Configuration,还处理注解中的 @Scope@Primary@Lazy 等元数据。
  • ClassPathBeanDefinitionScanner:当你调用 scan() 方法时,它便开始工作。它会根据指定的包路径扫描类文件,并根据过滤规则(默认是 @Component)把类找出来注册进容器。

2. 为什么手动 register 后必须 refresh()? 这是初学者最容易犯的错误。

  • 当你直接 new AnnotationConfigApplicationContext(AppConfig.class) 时,构造函数内部已经自动调用了 refresh()
  • 但当你使用无参构造函数并手动调用 register()scan() 时,容器处于“初始化中”状态。此时 Bean 定义已经加载,但 Bean 还没被创建出来,各种后置处理器(Post-Processor)也还没工作。
  • 必须手动调用 refresh(),容器才会真正“启动”:处理 Bean 之间的依赖、执行声明周期回调、创建单例对象。

3. 对普通类的“宽泛”支持 文档提到它可以接收“普通的 @Component 类”。实际上,它甚至可以接收完全没有任何注解的普通 Java 类。 如果你 register(MyPlainClass.class),Spring 也会把它当作一个 Bean 管理(虽然这时候你得手动注入它的依赖,或者它只有一个无参构造函数)。这在某些动态加载 Bean 的高级场景中非常有用。

4. 扫描范围的“元注解”陷阱 正如文档所说,@Configuration 也是一种 @Component。这意味着:

  • 如果你的扫描范围不小心覆盖了配置类,它们会被自动加载。
  • 在大型项目中,建议将配置类(@Configuration)和业务组件(@Service/@Controller)放在不同的子包中,虽然 Spring 能处理混在一起的情况,但清晰的分层会让你的扫描配置更有预测性。

5. 走向 Spring Boot 的桥梁 在 Spring Boot 中,你几乎从不手动 new AnnotationConfigApplicationContext。但 Spring Boot 底层根据你的应用类型,会自动创建 AnnotationConfigServletWebServerApplicationContext(Web 应用)或 AnnotationConfigApplicationContext(普通应用)。你今天学到的 registerrefresh 逻辑,正是 Boot 启动过程的核心。

Based on Spring Framework.