Appearance
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
注意上面的 userService
和 userController
方法使用了参数注入的方式。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
注解或重新设计你的类结构。
最佳实践总结
最佳实践
- 优先使用参数注入而不是方法调用来处理Bean依赖
- 合理选择Full/Lite模式,云原生应用推荐Lite模式
- 使用有意义的Bean名称,避免使用默认名称
- 适当使用@Qualifier来区分同类型的不同Bean
- 避免在@Bean方法中进行复杂的业务逻辑
总结 🎉
@Bean
和 @Configuration
注解是 Spring 现代化配置的核心,它们让我们告别了繁琐的 XML 配置,拥抱了类型安全的 Java 配置。
核心要点回顾:
- 🎯
@Configuration
类是Bean定义的来源,相当于XML配置文件 - 🏭
@Bean
方法是Bean的工厂方法,负责创建和配置对象 - 🔄 Full模式提供方法拦截,Lite模式提供更好的性能
- 💡 参数注入比方法调用更安全、更清晰
通过掌握这两个注解,你就能够构建出清晰、可维护、高性能的Spring应用配置。记住,好的配置不仅仅是让代码运行起来,更是要让后续的维护者能够轻松理解和修改! ✨