Skip to content

核心概念:@Bean@Configuration (Basic Concepts: @Bean and @Configuration)

Spring 的 Java 配置支持中,核心组件是标注了 @Configuration 的类和标注了 @Bean 的方法。

@Bean 注解用于指示一个方法会实例化、配置并初始化一个新对象,该对象由 Spring IoC 容器管理。对于那些熟悉 Spring 的 <beans/> XML 配置的人来说,@Bean 注解的作用与 <bean/> 元素完全相同。你可以在任何 Spring @Component 类中使用 @Bean 标注的方法。然而,它们最常与 @Configuration Bean 一起使用。

将一个类标注为 @Configuration 表示其主要目的是作为 Bean 定义的来源。此外,@Configuration 类允许通过调用同一类中的其他 @Bean 方法来定义 Bean 间的依赖关系。一个最简单的 @Configuration 类如下所示:

java
@Configuration
public class AppConfig {

	@Bean
	public MyServiceImpl myService() {
		return new MyServiceImpl();
	}
}
kotlin
@Configuration
class AppConfig {

	@Bean
	fun myService(): MyServiceImpl {
		return MyServiceImpl()
	}
}

上述 AppConfig 类等价于以下的 Spring <beans/> XML 配置:

xml
<beans>
	<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

配置类中是否应该在 @Bean 方法之间进行本地调用?

在常见场景中,@Bean 方法通常在 @Configuration 类内部声明,这样可以确保应用“完全(Full)”配置类处理,从而使跨方法引用被重定向到容器的生命周期管理。这可以防止同一 @Bean 方法被意外地通过普通的 Java 方法调用来执行,有助于减少难以追踪的微妙 Bug。

@Bean 方法声明在未标注 @Configuration 的类中,或者声明了 @Configuration(proxyBeanMethods=false) 时,它们被称为以“轻量(lite)”模式处理。在这种情况下,@Bean 方法实际上是一种通用的工厂方法机制,没有特殊的运行时处理(即不会为其生成 CGLIB 子类)。对这种方法的自定义 Java 调用不会被容器拦截,因此其行为就像普通的方法调用一样,每次都会创建一个新实例,而不是复用该 Bean 现有的单例(或作用域)实例。

因此,在没有运行时代理(即 Lite 模式)的情况下,类上的 @Bean 方法根本不应该声明 Bean 间的依赖关系。相反,它们应该操作其所属组件的字段,或者可选地操作工厂方法声明的参数,以便接收自动装配的协作对象。因此,此类 @Bean 方法永远不需要调用其他 @Bean 方法;每一个此类调用都可以通过工厂方法参数来表达。这样做的好处是运行时无需应用 CGLIB 子类化,从而减少了开销和内存占用。

接下来的章节将深入探讨 @Bean@Configuration 注解。但在那之前,我们将先介绍使用基于 Java 的配置创建 Spring 容器的各种方式。


补充教学 —— 深入理解 Full 模式与 Lite 模式

这是面试中关于 Spring Java 配置最高频的考点之一。

1. 为什么要用 CGLIB?(Full 模式的魔力) 在普通的 Java 代码中,如果你连续调用两次 myService() 方法,你肯定会得到两个不同的对象。 但在 Spring 的 @Configuration 类(默认 Full 模式)中:

java
@Configuration
public class MyConfig {
    @Bean
    public User user() {
        return new User("Tom");
    }

    @Bean
    public Order order() {
        // 直接调用上面的方法
        return new Order(user()); 
    }

    @Bean
    public Pay pay() {
        // 再次调用上面的方法
        return new Pay(user());
    }
}

因为 @Configuration 类被 CGLIB 代理了,Spring 会拦截对 user() 的调用。如果容器里已经有了 user 单例,它会直接返回旧的,而不是运行方法内部的 new User()。这保证了 OrderPay 引用的是同一个 User 实例。

2. 什么时候使用 Lite 模式? 当你设置 @Configuration(proxyBeanMethods = false) 时,你就进入了 Lite 模式。

  • 优点:启动速度更快,因为不需要生成 CGLIB 代理类。在 Spring Boot 的许多自动配置类中,为了极致的启动速度,通常会开启此选项。
  • 代价:你失去了直接调用 @Bean 方法来实现依赖注入的能力。

3. 如何在 Lite 模式下实现依赖注入? 既然不能直接调用方法,你应该使用方法参数注入

java
@Configuration(proxyBeanMethods = false)
public class MyLiteConfig {
    @Bean
    public User user() { ... }

    @Bean
    public Order order(User user) { // 通过参数传入,由 Spring 容器自动注入
        return new Order(user);
    }
}

这其实也是 Spring 官方更推荐的写法,因为它在 Full 和 Lite 模式下都能工作,且语义更清晰。

4. 总结建议

  • 如果你需要在配置类的方法之间互相调用且必须保证单例一致性,用默认的 Full 模式
  • 如果你追求极致性能,且所有的依赖都通过方法参数注入(从未在类中内部调用过 @Bean 方法),请使用 Lite 模式
  • 避坑指南:如果你在 @Component 类(而不是 @Configuration)里写 @Bean 方法,那就是 Lite 模式!千万不要在里面互相调用方法。

Based on Spring Framework.