Appearance
SpringBoot 配置类详解
概述
Spring Boot 推荐使用基于 Java 的配置方式。虽然可以在 SpringApplication
中使用 XML 源,但我们通常建议将单个 @Configuration
类作为主要配置源。通常,定义 main
方法的类是作为主要 @Configuration
的好选择。
TIP
互联网上发布了许多使用 XML 配置的 Spring 配置示例。如果可能的话,请始终尝试使用等效的基于 Java 的配置。搜索 Enable*
注解可能是一个很好的起点。
为什么使用配置类?
在传统的 Spring 开发中,我们通常使用 XML 文件来配置 Bean 和依赖关系。但是 XML 配置存在以下问题:
- 类型安全性差:XML 配置在编译时无法检查类型错误
- 重构困难:IDE 重构工具对 XML 支持有限
- 调试困难:运行时才能发现配置错误
- 代码分离:配置和代码分离,不利于理解和维护
SpringBoot 的配置类解决了这些问题:
基础配置类示例
让我们通过一个实际的电商系统示例来理解配置类的使用:
kotlin
package com.example.ecommerce
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.client.RestTemplate
/**
* 主应用程序类,同时也是主要的配置类
* @SpringBootApplication 包含了 @Configuration 注解
*/
@SpringBootApplication
class ECommerceApplication {
/**
* 配置 RestTemplate Bean,用于调用外部 API
* 比如调用支付网关、物流API等
*/
@Bean
fun restTemplate(): RestTemplate {
return RestTemplate()
}
/**
* 配置数据库连接池
*/
@Bean
fun dataSourceConfig(): DatabaseConfig {
return DatabaseConfig().apply {
maxPoolSize = 20
minPoolSize = 5
connectionTimeout = 30000
}
}
}
fun main(args: Array<String>) {
runApplication<ECommerceApplication>(*args)
}
java
package com.example.ecommerce;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**
* 主应用程序类,同时也是主要的配置类
*/
@SpringBootApplication
public class ECommerceApplication {
/**
* 配置 RestTemplate Bean
*/
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ECommerceApplication.class, args);
}
}
导入额外的配置类
您不需要将所有的 @Configuration
放在单个类中。可以使用 @Import
注解来导入额外的配置类。另外,您也可以使用 @ComponentScan
来自动扫描所有 Spring 组件,包括 @Configuration
类。
实际业务场景:模块化配置
在大型电商系统中,我们通常会将配置按模块分离:
kotlin
package com.example.ecommerce
import com.example.ecommerce.config.DatabaseConfig
import com.example.ecommerce.config.RedisConfig
import com.example.ecommerce.config.SecurityConfig
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Import
/**
* 主应用配置类
* 使用 @Import 导入其他模块的配置类
*/
@SpringBootApplication
@Import(
DatabaseConfig::class, // 数据库配置
RedisConfig::class, // Redis 缓存配置
SecurityConfig::class // 安全配置
)
class ECommerceApplication
fun main(args: Array<String>) {
runApplication<ECommerceApplication>(*args)
}
java
package com.example.ecommerce;
import com.example.ecommerce.config.DatabaseConfig;
import com.example.ecommerce.config.RedisConfig;
import com.example.ecommerce.config.SecurityConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
@SpringBootApplication
@Import({DatabaseConfig.class, RedisConfig.class, SecurityConfig.class})
public class ECommerceApplication {
public static void main(String[] args) {
SpringApplication.run(ECommerceApplication.class, args);
}
}
数据库配置类示例
kotlin
package com.example.ecommerce.config
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary
import javax.sql.DataSource
/**
* 数据库配置类
* 专门负责数据库相关的 Bean 配置
*/
@Configuration
class DatabaseConfig {
@Value("\${spring.datasource.url}")
private lateinit var dbUrl: String
@Value("\${spring.datasource.username}")
private lateinit var username: String
@Value("\${spring.datasource.password}")
private lateinit var password: String
/**
* 配置主数据源
* 用于用户、订单等核心业务数据
*/
@Bean
@Primary
fun primaryDataSource(): DataSource {
val config = HikariConfig().apply {
jdbcUrl = dbUrl
username = this@DatabaseConfig.username
password = this@DatabaseConfig.password
maximumPoolSize = 20 // 最大连接池大小
minimumIdle = 5 // 最小空闲连接数
connectionTimeout = 30000 // 连接超时时间
idleTimeout = 600000 // 空闲超时时间
maxLifetime = 1800000 // 连接最大生命周期
}
return HikariDataSource(config)
}
/**
* 配置只读数据源
* 用于报表查询等只读操作
*/
@Bean("readOnlyDataSource")
fun readOnlyDataSource(): DataSource {
val config = HikariConfig().apply {
jdbcUrl = "$dbUrl?readOnly=true"
username = this@DatabaseConfig.username
password = this@DatabaseConfig.password
maximumPoolSize = 10
minimumIdle = 2
isReadOnly = true // 设置为只读
}
return HikariDataSource(config)
}
}
java
package com.example.ecommerce.config;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
@Configuration
public class DatabaseConfig {
@Value("${spring.datasource.url}")
private String dbUrl;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Bean
@Primary
public DataSource primaryDataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(dbUrl);
config.setUsername(username);
config.setPassword(password);
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
return new HikariDataSource(config);
}
}
Redis 配置类示例
kotlin
package com.example.ecommerce.config
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer
import org.springframework.data.redis.serializer.StringRedisSerializer
/**
* Redis 配置类
* 负责缓存相关的配置
*/
@Configuration
class RedisConfig {
@Value("\${spring.redis.host:localhost}")
private lateinit var redisHost: String
@Value("\${spring.redis.port:6379}")
private var redisPort: Int = 6379
/**
* Redis 连接工厂配置
*/
@Bean
fun redisConnectionFactory(): RedisConnectionFactory {
val factory = JedisConnectionFactory()
factory.hostName = redisHost
factory.port = redisPort
factory.timeout = 2000 // 连接超时时间
return factory
}
/**
* Redis 模板配置
* 用于购物车、会话存储等
*/
@Bean
fun redisTemplate(connectionFactory: RedisConnectionFactory): RedisTemplate<String, Any> {
val template = RedisTemplate<String, Any>()
template.connectionFactory = connectionFactory
// 设置键的序列化方式
template.keySerializer = StringRedisSerializer()
template.hashKeySerializer = StringRedisSerializer()
// 设置值的序列化方式
template.valueSerializer = GenericJackson2JsonRedisSerializer()
template.hashValueSerializer = GenericJackson2JsonRedisSerializer()
template.afterPropertiesSet()
return template
}
}
java
package com.example.ecommerce.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Value("${spring.redis.host:localhost}")
private String redisHost;
@Value("${spring.redis.port:6379}")
private int redisPort;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(redisHost);
factory.setPort(redisPort);
return factory;
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
使用组件扫描
除了使用 @Import
,我们还可以使用 @ComponentScan
来自动扫描和加载配置类:
kotlin
package com.example.ecommerce
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.ComponentScan
/**
* 使用组件扫描自动发现配置类
* 扫描指定包下的所有 @Configuration 类
*/
@SpringBootApplication
@ComponentScan(
basePackages = [
"com.example.ecommerce.config", // 扫描配置包
"com.example.ecommerce.service", // 扫描服务包
"com.example.ecommerce.controller" // 扫描控制器包
]
)
class ECommerceApplication
fun main(args: Array<String>) {
runApplication<ECommerceApplication>(*args)
}
java
package com.example.ecommerce;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = {
"com.example.ecommerce.config",
"com.example.ecommerce.service",
"com.example.ecommerce.controller"
})
public class ECommerceApplication {
public static void main(String[] args) {
SpringApplication.run(ECommerceApplication.class, args);
}
}
> `@SpringBootApplication` 注解本身就包含了 `@ComponentScan`,默认会扫描主类所在包及其子包。只有当您需要扫描其他包时,才需要显式使用 `@ComponentScan`。
导入 XML 配置
如果您绝对必须使用基于 XML 的配置,我们建议您仍然从 @Configuration
类开始。然后可以使用 @ImportResource
注解来加载 XML 配置文件。
实际场景:整合遗留系统
在实际项目中,可能需要整合一些使用 XML 配置的遗留系统:
kotlin
package com.example.ecommerce
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.ImportResource
/**
* 导入 XML 配置文件
* 适用于需要兼容遗留系统的场景
*/
@SpringBootApplication
@ImportResource(
"classpath:legacy-beans.xml", // 遗留系统的Bean配置
"classpath:third-party-config.xml" // 第三方库的XML配置
)
class ECommerceApplication
fun main(args: Array<String>) {
runApplication<ECommerceApplication>(*args)
}
java
package com.example.ecommerce;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
@SpringBootApplication
@ImportResource({
"classpath:legacy-beans.xml",
"classpath:third-party-config.xml"
})
public class ECommerceApplication {
public static void main(String[] args) {
SpringApplication.run(ECommerceApplication.class, args);
}
}
XML 配置文件示例
xml
<!-- legacy-beans.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 遗留系统的订单处理器 -->
<bean id="legacyOrderProcessor"
class="com.legacy.system.OrderProcessor">
<property name="timeout" value="30000"/>
<property name="retryCount" value="3"/>
</bean>
<!-- 第三方支付网关配置 -->
<bean id="paymentGateway"
class="com.thirdparty.payment.Gateway">
<constructor-arg value="${payment.gateway.url}"/>
<constructor-arg value="${payment.gateway.key}"/>
</bean>
</beans>
配置类的最佳实践
1. 模块化配置
将不同功能模块的配置分离到不同的配置类中:
2. 条件化配置
使用条件注解来控制配置的生效条件:
kotlin
package com.example.ecommerce.config
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
/**
* 条件化配置示例
*/
@Configuration
class ConditionalConfig {
/**
* 只在开发环境生效的配置
*/
@Bean
@Profile("dev")
fun devMockService(): MockService {
return MockService("开发环境模拟服务")
}
/**
* 只在生产环境生效的配置
*/
@Bean
@Profile("prod")
fun prodRealService(): RealService {
return RealService("生产环境真实服务")
}
/**
* 根据配置属性决定是否创建Bean
*/
@Bean
@ConditionalOnProperty(
name = ["feature.email.enabled"],
havingValue = "true",
matchIfMissing = false
)
fun emailService(): EmailService {
return EmailService()
}
}
java
package com.example.ecommerce.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
public class ConditionalConfig {
@Bean
@Profile("dev")
public MockService devMockService() {
return new MockService("开发环境模拟服务");
}
@Bean
@Profile("prod")
public RealService prodRealService() {
return new RealService("生产环境真实服务");
}
@Bean
@ConditionalOnProperty(
name = "feature.email.enabled",
havingValue = "true",
matchIfMissing = false
)
public EmailService emailService() {
return new EmailService();
}
}
3. 配置属性绑定
使用 @ConfigurationProperties
来绑定外部配置:
kotlin
package com.example.ecommerce.config
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
/**
* 应用配置属性
* 绑定 application.yml 中的配置
*/
@Configuration
@ConfigurationProperties(prefix = "app")
data class AppConfig(
var name: String = "电商系统",
var version: String = "1.0.0",
var database: DatabaseProperties = DatabaseProperties(),
var redis: RedisProperties = RedisProperties(),
var email: EmailProperties = EmailProperties()
) {
data class DatabaseProperties(
var maxPoolSize: Int = 20,
var minPoolSize: Int = 5,
var connectionTimeout: Long = 30000
)
data class RedisProperties(
var timeout: Int = 2000,
var maxIdle: Int = 10,
var minIdle: Int = 2
)
data class EmailProperties(
var enabled: Boolean = false,
var host: String = "smtp.example.com",
var port: Int = 587,
var username: String = "",
var password: String = ""
)
}
java
package com.example.ecommerce.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "app")
public class AppConfig {
private String name = "电商系统";
private String version = "1.0.0";
private DatabaseProperties database = new DatabaseProperties();
private RedisProperties redis = new RedisProperties();
// getter 和 setter 方法
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public static class DatabaseProperties {
private int maxPoolSize = 20;
private int minPoolSize = 5;
private long connectionTimeout = 30000;
// getter 和 setter 方法
}
public static class RedisProperties {
private int timeout = 2000;
private int maxIdle = 10;
private int minIdle = 2;
// getter 和 setter 方法
}
}
对应的 application.yml
配置:
yaml
app:
name: 电商管理系统
version: 2.0.0
database:
max-pool-size: 50
min-pool-size: 10
connection-timeout: 60000
redis:
timeout: 5000
max-idle: 20
min-idle: 5
email:
enabled: true
host: smtp.company.com
port: 587
username: [email protected]
password: ${EMAIL_PASSWORD}
常见问题和解决方案
1. 循环依赖问题
WARNING
配置类之间可能会出现循环依赖问题,需要注意避免。
kotlin
// 错误示例 - 会导致循环依赖
@Configuration
class ConfigA {
@Autowired
private lateinit var serviceB: ServiceB
@Bean
fun serviceA(): ServiceA {
return ServiceA(serviceB) // 依赖 ServiceB
}
}
@Configuration
class ConfigB {
@Autowired
private lateinit var serviceA: ServiceA
@Bean
fun serviceB(): ServiceB {
return ServiceB(serviceA) // 依赖 ServiceA,形成循环
}
}
解决方案:
kotlin
@Configuration
class BetterConfig {
@Bean
fun serviceA(): ServiceA {
return ServiceA()
}
@Bean
fun serviceB(): ServiceB {
return ServiceB()
}
/**
* 通过方法参数注入,避免循环依赖
*/
@Bean
fun serviceIntegrator(serviceA: ServiceA, serviceB: ServiceB): ServiceIntegrator {
serviceA.setServiceB(serviceB)
serviceB.setServiceA(serviceA)
return ServiceIntegrator(serviceA, serviceB)
}
}
java
@Configuration
public class BetterConfig {
@Bean
public ServiceA serviceA() {
return new ServiceA();
}
@Bean
public ServiceB serviceB() {
return new ServiceB();
}
@Bean
public ServiceIntegrator serviceIntegrator(ServiceA serviceA, ServiceB serviceB) {
serviceA.setServiceB(serviceB);
serviceB.setServiceA(serviceA);
return new ServiceIntegrator(serviceA, serviceB);
}
}
2. Bean 重复定义
CAUTION
多个配置类定义相同名称的 Bean 可能会导致冲突。
解决方案:
kotlin
@Configuration
class DatabaseConfig {
@Bean("primaryDataSource") // 明确指定Bean名称
@Primary // 标记为主要Bean
fun primaryDataSource(): DataSource {
return createDataSource("primary")
}
@Bean("secondaryDataSource") // 明确指定不同的Bean名称
fun secondaryDataSource(): DataSource {
return createDataSource("secondary")
}
private fun createDataSource(name: String): DataSource {
// 创建数据源的逻辑
return HikariDataSource()
}
}
java
@Configuration
public class DatabaseConfig {
@Bean("primaryDataSource")
@Primary
public DataSource primaryDataSource() {
return createDataSource("primary");
}
@Bean("secondaryDataSource")
public DataSource secondaryDataSource() {
return createDataSource("secondary");
}
private DataSource createDataSource(String name) {
return new HikariDataSource();
}
}
总结
配置类是 SpringBoot 应用的重要组成部分,它提供了类型安全、易于维护的配置方式。通过合理的模块划分和最佳实践,可以构建出清晰、可维护的应用架构。
核心要点
- 优先使用 Java 配置:相比 XML 配置,Java 配置提供更好的类型安全性和 IDE 支持
- 模块化设计:将不同功能的配置分离到不同的配置类中
- 合理使用注解:
@Import
、@ComponentScan
、@Conditional
等注解的合理使用 - 避免常见陷阱:循环依赖、Bean 重复定义等问题的预防和解决
TIP
在实际项目中,建议按照功能模块来组织配置类,这样既便于维护,也有利于团队协作开发。
通过掌握这些配置类的使用技巧,您可以更好地构建和维护 SpringBoot 应用程序。