Skip to content

Spring Boot @Configuration 注解深度解析 ⚙️

什么是 @Configuration 注解?

@Configuration 是 Spring 框架中的一个类级别注解,它标识一个类作为 Bean 定义的配置源。简单来说,它就像是一个"Bean 工厂"的蓝图,告诉 Spring 容器:"这个类里面有很多创建 Bean 的方法,请按照我的指示来创建和管理这些对象"。

NOTE

@Configuration 类通过 @Bean 注解的方法来声明 Bean。这种方式提供了一种类型安全且可重构的配置方式,相比传统的 XML 配置更加灵活和强大。

为什么需要 @Configuration? 🤔

在没有 @Configuration 之前,我们通常使用 XML 配置文件来定义 Bean 及其依赖关系。但这种方式存在几个问题:

传统 XML 配置的痛点

  • 类型不安全:配置错误只能在运行时发现
  • 重构困难:修改类名需要同步更新 XML 文件
  • IDE 支持有限:缺乏自动补全和语法检查
  • 配置分散:业务逻辑和配置分离,维护困难

@Configuration 注解的出现,让我们可以用纯 Java 代码来配置 Spring 容器,解决了上述所有问题。

Bean 间依赖注入 🔗

基本概念

@Configuration 类中,Bean 之间的依赖关系可以通过简单的方法调用来表达。这是 @Configuration 最强大的特性之一。

kotlin
@Configuration
class AppConfig {
    
    @Bean
    fun userService(): UserService {
        // 直接调用其他 @Bean 方法来注入依赖
        return UserService(userRepository()) 
    }
    
    @Bean
    fun userRepository(): UserRepository {
        return UserRepository()
    }
}
xml
<!-- 传统 XML 配置方式 -->
<bean id="userService" class="com.example.UserService">
    <constructor-arg ref="userRepository"/>
</bean>

<bean id="userRepository" class="com.example.UserRepository"/>

实际业务场景示例

让我们看一个更贴近实际的例子:

kotlin
@Configuration
class DatabaseConfig {
    
    @Bean
    fun dataSource(): DataSource {
        val dataSource = HikariDataSource()
        dataSource.jdbcUrl = "jdbc:mysql://localhost:3306/myapp"
        dataSource.username = "root"
        dataSource.password = "password"
        return dataSource
    }
    
    @Bean
    fun jdbcTemplate(): JdbcTemplate {
        // 这里直接调用 dataSource() 方法来注入依赖
        return JdbcTemplate(dataSource()) 
    }
    
    @Bean
    fun transactionManager(): PlatformTransactionManager {
        // 同样通过方法调用来建立依赖关系
        return DataSourceTransactionManager(dataSource()) 
    }
}

IMPORTANT

这种方法调用的依赖注入方式只在 @Configuration 类中有效。如果在普通的 @Component 类中使用,每次方法调用都会创建新的实例,而不是获取 Spring 容器中的单例 Bean。

查找方法注入(Lookup Method Injection) 🔍

什么是查找方法注入?

查找方法注入是一个高级特性,主要用于解决单例 Bean 依赖原型 Bean 的场景。

TIP

想象这样一个场景:你有一个单例的服务类,但它需要在每次处理请求时都获取一个全新的命令对象。传统的依赖注入无法满足这个需求,因为单例 Bean 的依赖在创建时就已经确定了。

实际应用场景

kotlin
// 抽象的命令管理器
abstract class OrderProcessor {
    
    fun processOrder(orderData: OrderData): OrderResult {
        // 每次处理订单时都需要一个新的订单命令实例
        val orderCommand = createOrderCommand() 
        orderCommand.setOrderData(orderData)
        return orderCommand.execute()
    }
    
    // 抽象方法:如何获取新的命令实例?
    protected abstract fun createOrderCommand(): OrderCommand
}

使用 @Configuration 解决

kotlin
@Configuration
class OrderConfig {
    
    @Bean
    @Scope("prototype") // 原型作用域,每次获取都是新实例
    fun orderCommand(): OrderCommand {
        val command = OrderCommand()
        // 注入必要的依赖
        command.setPaymentService(paymentService())
        command.setInventoryService(inventoryService())
        return command
    }
    
    @Bean
    fun orderProcessor(): OrderProcessor {
        // 返回匿名实现,重写抽象方法
        return object : OrderProcessor() {
            override fun createOrderCommand(): OrderCommand {
                return orderCommand() 
            }
        }
    }
    
    @Bean
    fun paymentService(): PaymentService = PaymentService()
    
    @Bean
    fun inventoryService(): InventoryService = InventoryService()
}

时序图展示查找方法注入的工作流程

@Configuration 的内部工作机制 ⚙️

CGLIB 代理的魔法

当你在 @Configuration 类中多次调用同一个 @Bean 方法时,你可能会疑惑:这不会创建多个实例吗?

让我们看一个例子:

kotlin
@Configuration
class AppConfig {
    
    @Bean
    fun clientService1(): ClientService {
        val service = ClientServiceImpl()
        service.clientDao = clientDao() 
        return service
    }
    
    @Bean
    fun clientService2(): ClientService {
        val service = ClientServiceImpl()
        service.clientDao = clientDao() 
        return service
    }
    
    @Bean
    fun clientDao(): ClientDao {
        return ClientDaoImpl() // 这个方法被调用了两次!
    }
}

IMPORTANT

虽然 clientDao() 方法被调用了两次,但实际上只会创建一个 ClientDao 实例!这就是 Spring 的魔法所在。

CGLIB 工作原理

Spring 在启动时会使用 CGLIB 为所有 @Configuration 类创建子类代理。这个代理类会拦截对 @Bean 方法的调用:

避免 CGLIB 限制

如果你想避免 CGLIB 带来的限制(比如类不能是 final 的),可以使用以下方式:

kotlin
@Configuration(proxyBeanMethods = false) 
class AppConfig {
    
    @Bean
    fun clientService(clientDao: ClientDao): ClientService { 
        val service = ClientServiceImpl()
        service.clientDao = clientDao // 通过参数注入,而不是方法调用
        return service
    }
    
    @Bean
    fun clientDao(): ClientDao {
        return ClientDaoImpl()
    }
}
kotlin
@Component
class AppConfig {
    
    @Bean
    fun clientService(clientDao: ClientDao): ClientService {
        val service = ClientServiceImpl()
        service.clientDao = clientDao
        return service
    }
    
    @Bean
    fun clientDao(): ClientDao {
        return ClientDaoImpl()
    }
}

WARNING

当使用 proxyBeanMethods = false@Component 时,Bean 方法之间的调用不会被拦截,你必须完全依赖构造函数或方法参数的依赖注入。

最佳实践与建议 ⭐

1. 合理组织配置类

kotlin
// ✅ 推荐:按功能模块划分配置类
@Configuration
class DatabaseConfig {
    // 数据库相关的Bean配置
}

@Configuration
class SecurityConfig {
    // 安全相关的Bean配置
}

@Configuration
class WebConfig {
    // Web相关的Bean配置
}

2. 使用条件注解

kotlin
@Configuration
class AppConfig {
    
    @Bean
    @ConditionalOnProperty(name = "app.cache.enabled", havingValue = "true")
    fun cacheManager(): CacheManager {
        return ConcurrentMapCacheManager("users", "orders")
    }
    
    @Bean
    @Profile("development")
    fun developmentDataSource(): DataSource {
        // 开发环境的数据源配置
        return EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .build()
    }
}

3. 利用配置属性

kotlin
@Configuration
@EnableConfigurationProperties(AppProperties::class)
class AppConfig(private val appProperties: AppProperties) {
    
    @Bean
    fun apiClient(): ApiClient {
        return ApiClient().apply {
            baseUrl = appProperties.api.baseUrl
            timeout = appProperties.api.timeout
        }
    }
}

@ConfigurationProperties(prefix = "app")
data class AppProperties(
    val api: ApiConfig = ApiConfig()
) {
    data class ApiConfig(
        val baseUrl: String = "http://localhost:8080",
        val timeout: Duration = Duration.ofSeconds(30)
    )
}

总结 🎉

@Configuration 注解是 Spring Boot 中非常重要的配置机制,它提供了:

  • 类型安全的配置方式
  • 灵活的依赖注入机制
  • 强大的 Bean 生命周期管理
  • 与现代 IDE 的完美集成

通过理解其内部工作机制(CGLIB 代理),我们可以更好地利用这个强大的工具来构建健壮、可维护的 Spring Boot 应用程序。

TIP

记住:@Configuration 不仅仅是一个注解,它代表了一种现代化的、类型安全的配置哲学。掌握它,你就掌握了 Spring Boot 配置的精髓!