Skip to content

Spring @Bean 和 @Configuration 注解详解 ☕

前言:为什么需要 @Bean 和 @Configuration? 🤔

在传统的 Spring 开发中,我们需要编写大量的 XML 配置文件来定义 Bean。想象一下,每当你需要创建一个服务对象时,都要在 XML 文件中写一堆配置:

xml
<beans>
    <bean id="userService" class="com.example.UserService"/>
    <bean id="orderService" class="com.example.OrderService">
        <property name="userService" ref="userService"/>
    </bean>
</beans>

这种方式存在几个痛点:

  • 📝 配置繁琐,容易出错
  • 🔍 IDE 支持不够友好,缺乏类型检查
  • 🚀 重构困难,修改类名需要同步修改 XML
  • 📚 配置分散,难以维护

Spring 的 @Bean@Configuration 注解就是为了解决这些问题而生的!它们让我们可以用纯 Java 代码来配置 Spring 容器,享受 IDE 的智能提示和类型安全。

TIP

可以把 @Configuration 类想象成一个"Bean 工厂",而 @Bean 方法就是这个工厂里的"生产线",每条生产线负责创建一个特定的产品(Bean)。

核心概念解析 ⚙️

@Configuration:配置类的标识符

@Configuration 注解标识一个类作为 Bean 定义的来源。被此注解标记的类相当于传统的 XML 配置文件。

@Bean:Bean 定义的方法

@Bean 注解用于方法上,表示该方法会实例化、配置并初始化一个由 Spring IoC 容器管理的对象。

基础用法示例 🛠️

让我们通过一个实际的业务场景来理解这两个注解的用法:

kotlin
<!-- applicationContext.xml -->
<beans>
    <bean id="userRepository" class="com.example.repository.UserRepository"/>
    <bean id="userService" class="com.example.service.UserService">
        <constructor-arg ref="userRepository"/>
    </bean>
</beans>
kotlin
@Configuration
class AppConfig {
    
    @Bean
    fun userRepository(): UserRepository {
        // 创建并配置 UserRepository
        return UserRepository()
    }
    
    @Bean
    fun userService(): UserService {
        // 通过调用其他 @Bean 方法来注入依赖
        return UserService(userRepository()) 
    }
}

IMPORTANT

@Configuration 类中,Bean 方法之间的调用会被 Spring 拦截,确保单例模式。即使你多次调用 userRepository(),Spring 也只会创建一个实例。

完整的业务场景示例 🚀

让我们构建一个完整的用户管理系统来展示 @Bean@Configuration 的强大之处:

kotlin
// 数据层
class UserRepository {
    private val users = mutableMapOf<Long, User>()
    
    fun save(user: User): User {
        users[user.id] = user
        println("用户已保存: ${user.name}")
        return user
    }
    
    fun findById(id: Long): User? = users[id]
}

// 业务层
class UserService(private val userRepository: UserRepository) {
    
    fun createUser(name: String, email: String): User {
        val user = User(System.currentTimeMillis(), name, email)
        return userRepository.save(user)
    }
    
    fun getUser(id: Long): User? {
        return userRepository.findById(id)
    }
}

// 控制层
@RestController
class UserController(private val userService: UserService) {
    
    @PostMapping("/users")
    fun createUser(@RequestBody request: CreateUserRequest): User {
        return userService.createUser(request.name, request.email)
    }
    
    @GetMapping("/users/{id}")
    fun getUser(@PathVariable id: Long): User? {
        return userService.getUser(id)
    }
}

// 数据模型
data class User(val id: Long, val name: String, val email: String)
data class CreateUserRequest(val name: String, val email: String)

现在,让我们用 @Configuration@Bean 来配置这些组件:

kotlin
@Configuration
class AppConfig {
    
    @Bean
    fun userRepository(): UserRepository {
        println("创建 UserRepository Bean") 
        return UserRepository()
    }
    
    @Bean
    fun userService(userRepository: UserRepository): UserService { 
        println("创建 UserService Bean,注入 UserRepository")
        return UserService(userRepository)
    }
    
    @Bean
    fun userController(userService: UserService): UserController { 
        println("创建 UserController Bean,注入 UserService")
        return UserController(userService)
    }
}

NOTE

注意上面的 userServiceuserController 方法使用了参数注入的方式。Spring 会自动将对应的 Bean 注入到方法参数中,这比方法调用的方式更加清晰和推荐。

Bean 创建的生命周期流程 ♻️

让我们通过时序图来理解 Spring 容器如何处理 @Configuration 类中的 @Bean 方法:

高级特性:proxyBeanMethods 详解 ⚙️

Spring 5.2 引入了 proxyBeanMethods 属性,让我们可以控制 @Configuration 类的代理行为:

kotlin
@Configuration // proxyBeanMethods = true (默认)
class FullModeConfig {
    
    @Bean
    fun serviceA(): ServiceA {
        println("创建 ServiceA")
        return ServiceA()
    }
    
    @Bean
    fun serviceB(): ServiceB {
        // 这里的 serviceA() 调用会被代理拦截
        // 确保返回同一个 ServiceA 实例
        return ServiceB(serviceA()) 
    }
}
kotlin
@Configuration(proxyBeanMethods = false) 
class LiteModeConfig {
    
    @Bean
    fun serviceA(): ServiceA {
        println("创建 ServiceA")
        return ServiceA()
    }
    
    @Bean
    fun serviceB(): ServiceB {
        // 这里的 serviceA() 调用不会被拦截
        // 每次调用都会创建新的 ServiceA 实例!
        return ServiceB(serviceA()) 
    }
}

WARNING

在 Lite 模式下,Bean 方法之间的直接调用不会被 Spring 拦截,可能导致创建多个实例。建议使用参数注入的方式来避免这个问题。

让我们看看推荐的 Lite 模式用法:

kotlin
@Configuration(proxyBeanMethods = false)
class RecommendedLiteModeConfig {
    
    @Bean
    fun serviceA(): ServiceA {
        return ServiceA()
    }
    
    @Bean
    fun serviceB(serviceA: ServiceA): ServiceB { 
        // 使用参数注入,Spring会自动注入ServiceA的单例
        return ServiceB(serviceA)
    }
}

性能对比和选择建议 📈

特性Full模式Lite模式
CGLIB代理✅ 是❌ 否
方法拦截✅ 是❌ 否
启动性能🐌 较慢🚀 较快
内存占用📈 较高📉 较低
Bean间调用安全✅ 安全⚠️ 需注意

选择建议

  • Full模式:适合复杂的配置场景,需要Bean间方法调用的情况
  • Lite模式:适合简单配置,追求启动性能的场景,特别是在云原生环境中

实战:构建一个完整的配置类 🏗️

让我们构建一个真实项目中常见的配置类,展示各种配置技巧:

完整的配置类示例
kotlin
@Configuration
@EnableConfigurationProperties(AppProperties::class)
class DatabaseConfig(
    private val appProperties: AppProperties
) {
    
    @Bean
    @Primary // 标记为主要的数据源
    fun primaryDataSource(): DataSource {
        return HikariDataSource().apply {
            jdbcUrl = appProperties.database.primaryUrl
            username = appProperties.database.username
            password = appProperties.database.password
            maximumPoolSize = 10
        }
    }
    
    @Bean
    @Qualifier("readonly") // 使用限定符区分不同的Bean
    fun readonlyDataSource(): DataSource {
        return HikariDataSource().apply {
            jdbcUrl = appProperties.database.readonlyUrl
            username = appProperties.database.username
            password = appProperties.database.password
            maximumPoolSize = 5
            isReadOnly = true
        }
    }
    
    @Bean
    fun transactionManager(
        @Qualifier("primary") dataSource: DataSource
    ): PlatformTransactionManager {
        return DataSourceTransactionManager(dataSource)
    }
    
    @Bean
    @ConditionalOnProperty(
        name = ["app.cache.enabled"], 
        havingValue = "true"
    )
    fun cacheManager(): CacheManager {
        return ConcurrentMapCacheManager("users", "orders")
    }
}

@ConfigurationProperties(prefix = "app")
data class AppProperties(
    val database: DatabaseProperties
) {
    data class DatabaseProperties(
        val primaryUrl: String,
        val readonlyUrl: String,
        val username: String,
        val password: String
    )
}

常见陷阱和最佳实践 ⚠️

陷阱1:在非@Configuration类中使用@Bean

kotlin
@Component // 不是 @Configuration!
class WrongConfig {
    
    @Bean
    fun serviceA(): ServiceA {
        return ServiceA()
    }
    
    @Bean
    fun serviceB(): ServiceB {
        // 危险!这里会创建新的ServiceA实例
        return ServiceB(serviceA()) 
    }
}

陷阱2:循环依赖

kotlin
@Configuration
class CircularConfig {
    
    @Bean
    fun serviceA(serviceB: ServiceB): ServiceA { 
        return ServiceA(serviceB)
    }
    
    @Bean
    fun serviceB(serviceA: ServiceA): ServiceB { 
        return ServiceB(serviceA)
    }
}

CAUTION

避免循环依赖!如果必须处理复杂依赖关系,考虑使用 @Lazy 注解或重新设计你的类结构。

最佳实践总结

最佳实践

  1. 优先使用参数注入而不是方法调用来处理Bean依赖
  2. 合理选择Full/Lite模式,云原生应用推荐Lite模式
  3. 使用有意义的Bean名称,避免使用默认名称
  4. 适当使用@Qualifier来区分同类型的不同Bean
  5. 避免在@Bean方法中进行复杂的业务逻辑

总结 🎉

@Bean@Configuration 注解是 Spring 现代化配置的核心,它们让我们告别了繁琐的 XML 配置,拥抱了类型安全的 Java 配置。

核心要点回顾:

  • 🎯 @Configuration 类是Bean定义的来源,相当于XML配置文件
  • 🏭 @Bean 方法是Bean的工厂方法,负责创建和配置对象
  • 🔄 Full模式提供方法拦截,Lite模式提供更好的性能
  • 💡 参数注入比方法调用更安全、更清晰

通过掌握这两个注解,你就能够构建出清晰、可维护、高性能的Spring应用配置。记住,好的配置不仅仅是让代码运行起来,更是要让后续的维护者能够轻松理解和修改! ✨