ExecutorService线程池工作线程无法调用Autowired成员方法问题排查
兄弟,你的问题其实出在Spring Bean的创建方式和线程池任务的异常处理这两个核心点上,咱们一步步拆解清楚:
1. 手动new的Spring Bean不受容器管理,Autowired成员未被注入
你在processWatermarkEventObjects方法里直接new WatermarkEventProcessorService(watermarkEventObject)创建实例,但这个实例完全脱离了Spring容器的控制——Spring只会对它自己初始化的Bean执行@Autowired注入操作,手动new出来的对象里的apiService根本就是null!
而且你在WatermarkEventProcessorService里给watermarkEventObject同时加了@Autowired和构造函数赋值,这本身就是逻辑矛盾:Spring会尝试自动注入这个对象,但你又手动覆盖了它,完全打乱了Spring的依赖注入逻辑。
2. 线程池submit的任务异常被隐藏,导致无日志输出
当你调用apiService.getApplicationByPrismId()时,因为apiService是null,会抛出NullPointerException,但你用的是executor.submit(Runnable)——这个方法会把异常封装在返回的Future对象里,如果你没有调用Future.get()去获取结果,这个异常就会被默默吞掉,控制台自然看不到任何报错,线程就直接悄无声息地终止了。
这就是为什么日志只输出到about to invoke apiService.getApplicationByPrismId(),后面的内容完全没动静的核心原因。
方案一:拆分业务类和任务类(推荐,职责更清晰)
把WatermarkEventProcessorService拆成两个类:一个Spring管理的服务类负责核心业务逻辑,一个独立的Runnable任务类负责线程执行:
// Spring管理的业务服务类 @Service public class WatermarkEventService { @Autowired private ApplicationAPIService apiService; public boolean prismIdExists(WatermarkEventObject watermarkEventObject) { return watermarkEventObject.getPrismId() != null && !watermarkEventObject.getPrismId().isEmpty(); } public ApplicationRecord findApplicationRecordByPrismId(String prismId) { System.out.println("Inside findApplicationByPrismId"); System.out.println("about to invoke apiService.getApplicationByPrismId()"); ApplicationRecord applicationRecord = apiService.getApplicationByPrismId(prismId); System.out.println("returned from apiService.getApplicationByPrismId()"); return applicationRecord; } } // 独立的线程任务类,无需Spring管理 public class WatermarkEventTask implements Runnable { private final WatermarkEventService eventService; private final WatermarkEventObject watermarkEventObject; public WatermarkEventTask(WatermarkEventService eventService, WatermarkEventObject watermarkEventObject) { this.eventService = eventService; this.watermarkEventObject = watermarkEventObject; } @Override public void run() { try { System.out.println("Starting Processor thread."); if (eventService.prismIdExists(watermarkEventObject)) { System.out.println("within the if block"); ApplicationRecord appRecord = eventService.findApplicationRecordByPrismId(watermarkEventObject.getPrismId()); System.out.println("Returned from findApplicationByPrismId"); } System.out.println("Completed Processor thread."); } catch (Exception e) { // 强制捕获所有异常,避免线程默默挂掉 System.err.println("Processor thread failed: " + e.getMessage()); e.printStackTrace(); } } }
然后修改线程池的调用逻辑,注入Spring管理的业务服务:
@Autowired private WatermarkEventService watermarkEventService; public void processWatermarkEventObjects(List<WatermarkEventObject> waterEventObjectList) { ExecutorService executor = Executors.newFixedThreadPool(1); for (WatermarkEventObject watermarkEventObject : waterEventObjectList) { executor.submit(new WatermarkEventTask(watermarkEventService, watermarkEventObject)); } executor.shutdown(); }
方案二:用Spring的原型Bean(适合简单场景)
如果不想拆分类,可以把WatermarkEventProcessorService的作用域设为prototype(每次从容器获取都是新实例),然后通过Spring容器获取实例并设置事件对象:
@Service @Scope("prototype") // 标记为原型Bean,每次获取都是新实例 public class WatermarkEventProcessorService implements Runnable { @Autowired private ApplicationAPIService apiService; // 去掉@Autowired,用setter传递事件对象 private WatermarkEventObject watermarkEventObject; public void setWatermarkEventObject(WatermarkEventObject watermarkEventObject) { this.watermarkEventObject = watermarkEventObject; } // 原有业务方法不变... @Override public void run() { try { // 原有执行逻辑 } catch (Exception e) { e.printStackTrace(); } } }
调用时从Spring容器获取实例:
@Autowired private ApplicationContext applicationContext; public void processWatermarkEventObjects(List<WatermarkEventObject> waterEventObjectList) { ExecutorService executor = Executors.newFixedThreadPool(1); for (WatermarkEventObject watermarkEventObject : waterEventObjectList) { WatermarkEventProcessorService processor = applicationContext.getBean(WatermarkEventProcessorService.class); processor.setWatermarkEventObject(watermarkEventObject); executor.submit(processor); } executor.shutdown(); }
额外提醒:务必处理线程池任务的异常
无论用哪种方案,都要在run()方法里加上try-catch捕获所有异常,或者改用executor.execute(Runnable)(这个方法会直接把异常输出到控制台),避免异常被默默吞掉,方便后续排查问题。
内容的提问来源于stack exchange,提问作者kramsiv1234

