背景
其它厂家调用我们应用的接口把处理后的数据回传过来,是http 协议的接口,这边会进行入库下载文件的逻辑,比较耗时,为了防止服务的阻塞,这边接受到数据进行了简单的校验后构建了一个本地队列,然后开启一个一直消费这个队列的功能,问题就是在实现这个功能的过程中产生。
实现
@PostConstruct
public void consume() {
while (!Thread.currentThread().isInterrupted()) {
AlgorithmAlarmReviewRecordDTO algorithmAlarmReviewRecordDTO = alarmReviewDataQueue.poll();
if (algorithmAlarmReviewRecordDTO == null) {
continue;
}
List<PointInfo> pointInfo = algorithmAlarmReviewRecordDTO.getPointInfo();
String stationCode = algorithmAlarmReviewRecordDTO.getStationCode();
StationInfoDTO stationInfoDTO = stationApiService.searchByPmsCode(stationCode);
if (CollectionUtil.isEmpty(pointInfo)) {
log.error("点位信息为空 pointInfo is null");
return;
}
for (PointInfo info : pointInfo) {
List<AlarmInfo> alarmInfo = info.getAlarmInfo();
if (CollectionUtil.isEmpty(alarmInfo)) {
log.error("报警信息为空 alarmInfo is null");
return;
}
for (AlarmInfo alarm : alarmInfo) {
AlgorithmAlarmReviewRecord algorithmAlarmReviewRecord = new AlgorithmAlarmReviewRecord();
BeanUtils.copyProperties(algorithmAlarmReviewRecordDTO, algorithmAlarmReviewRecord);
algorithmAlarmReviewRecord.setTaskRecordTime(DateUtil.str2Timestamp(algorithmAlarmReviewRecordDTO.getTaskRecordTime()));
algorithmAlarmReviewRecord.setAreaName(info.getAreaName());
algorithmAlarmReviewRecord.setBayName(info.getBayName());
algorithmAlarmReviewRecord.setPointId(info.getPointId());
algorithmAlarmReviewRecord.setPointName(info.getPointName());
algorithmAlarmReviewRecord.setRectangle(alarm.getRectangle());
algorithmAlarmReviewRecord.setAlarmState(alarm.getAlarmState());
//alarm.getImageBase64()
String fileId = handleImage(stationInfoDTO, alarm.getImageBase64());
algorithmAlarmReviewRecord.setFileId(fileId);
algorithmAlarmReviewRecord.setTime(DateUtil.str2Timestamp(alarm.getTime()));
algorithmAlarmReviewRecordMapper.insert(algorithmAlarmReviewRecord);
}
}
}
}
现象
postman 调用不了接口,定睛一看服务一直启动不成功。
整改
@PostConstruct
public void consume() {
Thread consumerThread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
AlgorithmAlarmReviewRecordDTO algorithmAlarmReviewRecordDTO = alarmReviewDataQueue.poll();
if (algorithmAlarmReviewRecordDTO == null) {
ThreadUtil.safeSleep(1000);
continue;
}
try {
List<PointInfo> pointInfo = algorithmAlarmReviewRecordDTO.getPointInfo();
String stationCode = algorithmAlarmReviewRecordDTO.getStationCode();
StationInfoDTO stationInfoDTO = stationApiService.searchByPmsCode(stationCode);
if (CollectionUtil.isEmpty(pointInfo)) {
log.error("点位信息为空 pointInfo is null");
continue;
}
for (PointInfo info : pointInfo) {
List<AlarmInfo> alarmInfo = info.getAlarmInfo();
if (CollectionUtil.isEmpty(alarmInfo)) {
log.error("报警信息为空 alarmInfo is null");
continue;
}
for (AlarmInfo alarm : alarmInfo) {
AlgorithmAlarmReviewRecord algorithmAlarmReviewRecord = new AlgorithmAlarmReviewRecord();
BeanUtils.copyProperties(algorithmAlarmReviewRecordDTO, algorithmAlarmReviewRecord);
algorithmAlarmReviewRecord.setStationId(stationInfoDTO.getStationId());
algorithmAlarmReviewRecord.setTaskRecordTime(DateUtil.str2Timestamp(algorithmAlarmReviewRecordDTO.getTaskRecordTime()));
algorithmAlarmReviewRecord.setAreaName(info.getAreaName());
algorithmAlarmReviewRecord.setBayName(info.getBayName());
algorithmAlarmReviewRecord.setMainDeviceName(info.getMainDeviceName());
algorithmAlarmReviewRecord.setComponentName(info.getComponentName());
algorithmAlarmReviewRecord.setPointId(info.getPointId());
algorithmAlarmReviewRecord.setPointName(info.getPointName());
algorithmAlarmReviewRecord.setDefectType(alarm.getDefectType());
algorithmAlarmReviewRecord.setTime(DateUtil.str2Timestamp(alarm.getTime()));
algorithmAlarmReviewRecord.setRectangle(alarm.getRectangle());
algorithmAlarmReviewRecord.setAlarmState(alarm.getAlarmState());
String fileId = handleImage(stationInfoDTO, alarm.getImageBase64());
algorithmAlarmReviewRecord.setFileId(fileId);
algorithmAlarmReviewRecord.setCreateTime(System.currentTimeMillis());
algorithmAlarmReviewRecordMapper.insert(algorithmAlarmReviewRecord);
}
}
} catch (Exception e) {
log.error("处理告警审核数据报错:{},{}", e, e.getMessage());
}
}
});
consumerThread.setName("alarm-review-data-consumer");
consumerThread.start();
}
原因
项目启动不成功是因为你在 @PostConstruct 方法中使用了无限循环,这会阻塞应用的启动过程。@PostConstruct 注解的方法在 Spring 容器初始化完成后立即执行,并且必须执行完毕后应用才能完全启动。
问题分析:
@PostConstruct 方法中的无限循环会阻止 Spring Boot 应用完成启动
即使添加了 Thread.currentThread().isInterrupted() 检查,主线程也不会被中断,所以循环不会停止
这导致应用无法完成初始化,外部请求(如 Postman)无法被处理
总结
修改要点:
将无限循环包装在独立的线程中执行
给线程设置有意义的名称,便于调试和监控
添加异常处理机制,防止线程因异常而终止
在队列为空时添加短暂休眠,避免CPU空转
在异常情况下也添加休眠,防止异常频繁重试
这样修改后,Spring Boot 应用应该能够正常启动,因为 @PostConstruct 方法会立即返回,而消费逻辑在独立线程中执行。