使用 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 容器,如下例所示:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}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 注解的类都可以作为参数提供给构造函数,如下例所示:
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}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()
}前面的例子假设 MyServiceImpl、Dependency1 和 Dependency2 使用了 Spring 的依赖注入注解,例如 @Autowired。
使用 register(Class<?>...) 编程式构建容器
你可以使用无参构造函数实例化 AnnotationConfigApplicationContext,然后使用 register() 方法对其进行配置。这种方法在编程式构建 AnnotationConfigApplicationContext 时特别有用。以下示例展示了如何操作:
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();
}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 类:
@Configuration
@ComponentScan(basePackages = "com.acme") // (1)
public class AppConfig {
// ...
}@Configuration
@ComponentScan(basePackages = ["com.acme"]) // (1)
class AppConfig {
// ...
}- 此注解用于启用组件扫描。
提示
经验丰富的 Spring 用户可能熟悉 Spring context: 命名空间中等价的 XML 声明,如下例所示:
<beans>
<context:component-scan base-package="com.acme"/>
</beans>在前面的例子中,将扫描 com.acme 包以查找任何标注了 @Component 的类,并将这些类注册为容器中的 Spring Bean 定义。AnnotationConfigApplicationContext 提供了 scan(String...) 方法,以实现相同的组件扫描功能,如下例所示:
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.acme");
ctx.refresh();
MyService myService = ctx.getBean(MyService.class);
}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
AnnotationConfigApplicationContext 的 WebApplicationContext 变体是 AnnotationConfigWebApplicationContext。在配置 Spring ContextLoaderListener Servlet 监听器、Spring MVC DispatcherServlet 等组件时,可以使用此实现。以下 web.xml 代码片段配置了一个典型的 Spring MVC Web 应用程序(注意 contextClass 上下文参数和初始化参数的使用):
<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(普通应用)。你今天学到的 register 和 refresh 逻辑,正是 Boot 启动过程的核心。