Spring Boot的自动配置功能极大地提升了开发效率,但如果不深入理解其工作原理,可能会在复杂场景下遇到意想不到的问题。本文将分享三次实际项目中的踩坑经历,并解析如何正确使用自动配置。
第一次踩坑:数据源自动配置导致数据库连接池溢出
在开发后台管理系统时,最初只需连接单个MySQL数据库。随着业务扩展,需要新增历史数据库进行数据查询。
依赖配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
配置文件:
spring:
datasource:
url: jdbc:mysql://localhost:3306/main_db?useUnicode=true&characterEncoding=utf8
username: root
password: root
historical:
url: jdbc:mysql://localhost:3306/his_db?useUnicode=true&characterEncoding=utf8
username: root
password: root
应用启动后运行正常,但上线两小时后监控报警显示主数据库连接数达到上限200,大量业务请求被阻塞。
排查发现日志中存在两个数据源初始化记录:
2023-05-15 02:15:32.123 INFO c.a.d.s.b.a.DruidDataSourceAutoConfigure - Init DruidDataSource
2023-05-15 02:15:32.456 INFO o.s.j.d.DriverManagerDataSource - Loaded JDBC driver: com.mysql.cj.jdbc.Driver
分析DataSourceAutoConfiguration源码发现关键条件注解:
@AutoConfiguration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
static class PooledDataSourceConfiguration {
// 若容器中不存在DataSource实例则自动配置
}
}
解决方案是通过显式配置数据源Bean来覆盖默认行为:
@Configuration
public class DataSourceConfig {
@Bean
@Primary
@ConfigurationProperties("spring.datasource.main")
public DataSource mainDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.historical")
public DataSource historicalDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
public JdbcTemplate historicalJdbcTemplate(@Qualifier("historicalDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
优化后的配置:
spring:
datasource:
main:
url: jdbc:mysql://localhost:3306/main_db?useUnicode=true&characterEncoding=utf8
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 5
max-active: 20
min-idle: 5
historical:
url: jdbc:mysql://localhost:3306/his_db?useUnicode=true&characterEncoding=utf8
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 3
max-active: 10
min-idle: 3
使用方式:
@Service
public class HistoricalService {
@Autowired
@Qualifier("historicalJdbcTemplate")
private JdbcTemplate historicalJdbcTemplate;
public List<HistoryData> queryHistory() {
return historicalJdbcTemplate.query(...);
}
}
关键收获:理解自动配置的条件触发机制,通过显式声明Bean来引导配置行为。
第二次踩坑:Redis序列化配置导致缓存数据异常
引入Redis缓存依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
业务代码:
@Service
public class ProductService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public Product getProduct(Long id) {
String key = "product:" + id;
Product product = (Product) redisTemplate.opsForValue().get(key);
if (product == null) {
product = productMapper.selectById(id);
redisTemplate.opsForValue().set(key, product, 30, TimeUnit.MINUTES);
}
return product;
}
}
上线后发现商品价格更新后前端仍显示旧数据。通过redis-cli检查发现缓存值为JDK序列化格式:
127.0.0.1:6379> get "product:123"
"\xac\xed\x00\x05sr\x00\x15com.example.Product\x00\x00\x00\x00\x00\x00\x00\x01\x00\x05I\x00\x02idI\x00\x06status..."
分析RedisAutoConfiguration源码:
@AutoConfiguration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 默认使用JDK序列化
return template;
}
}
通过自定义配置解决序列化问题:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
template.setValueSerializer(serializer);
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
核心思路:利用条件注解机制,通过声明同名Bean覆盖默认配置,同时保留数据源连接工厂等自动配置功能。
第三次踩坑:Jackson日期序列化格式不匹配
实体类定义:
@Data
public class OrderVO {
private Long id;
private String orderNo;
private Date createTime;
}
Controller接口返回的时间字段显示为时间戳格式(如1684108800000),而前端需要yyyy-MM-dd HH:mm:ss格式。
分析JacksonAutoConfiguration源码:
@AutoConfiguration
@ConditionalOnClass(ObjectMapper.class)
public class JacksonAutoConfiguration {
@Bean
@Primary
@ConditionalOnMissingBean
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
return builder.createXmlMapper(false).build();
}
}
通过配置文件定制序列化行为:
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: Asia/Shanghai
serialization:
write-dates-as-timestamps: false
default-property-inclusion: non_null
如需更精细控制,可使用定制器:
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() {
return builder -> {
builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
builder.timeZone(TimeZone.getTimeZone("Asia/Shanghai"));
builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss");
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
};
}
}
实体类优化:
@Data
public class OrderVO {
private Long id;
private String orderNo;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private Date createTime;
}
总结:掌握自动配置的正确使用方式
基于三次实践经历,对Spring Boot自动配置得出以下结论:
自动配置核心机制
- 条件触发:基于@ConditionalOnXxx注解判断是否生效
- 缺省配置:容器中不存在时才创建默认Bean
- 配置驱动:通过属性文件影响组件行为
最佳实践原则
-
优先使用配置定制
通过配置文件或Customizer调整行为,避免完全重写
-
理解条件注解
利用@ConditionalOnMissingBean机制进行有控制的覆盖
-
保留自动配置优势
让框架处理基础配置,专注业务定制
技术要点
- 数据源配置:通过显式Bean声明避免多数据源冲突
- Redis序列化:自定义Template覆盖默认序列化方案
- 日期格式:通过Jackson配置或注解指定序列化格式
自动配置要用,但更要懂其原理。在享受便利的同时,通过理解机制来避免潜在问题。