找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

211

积分

0

好友

23

主题
发表于 昨天 01:44 | 查看: 5| 回复: 0

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
  • 配置驱动:通过属性文件影响组件行为

最佳实践原则

  1. 优先使用配置定制 通过配置文件或Customizer调整行为,避免完全重写

  2. 理解条件注解 利用@ConditionalOnMissingBean机制进行有控制的覆盖

  3. 保留自动配置优势 让框架处理基础配置,专注业务定制

技术要点

  • 数据源配置:通过显式Bean声明避免多数据源冲突
  • Redis序列化:自定义Template覆盖默认序列化方案
  • 日期格式:通过Jackson配置或注解指定序列化格式

自动配置要用,但更要懂其原理。在享受便利的同时,通过理解机制来避免潜在问题。

您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区(YunPan.Plus) ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-1 14:12 , Processed in 0.060745 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 CloudStack.

快速回复 返回顶部 返回列表