使用自定义Callable实现类时出现Null Pointer Exception问题求助
首先,你遇到的Null Pointer Exception核心原因很清晰:Spring不会对手动new出来的ProcessingThread实例进行依赖注入,所以类里的dataRetrievalService始终是null,当call()方法调用它的getTimeSeriesData()时自然会抛出NPE。而匿名内部类写法能正常运行,是因为匿名类是在Spring管理的Bean(你的调用方类)内部创建的,它可以直接引用调用方类中已经被Spring注入好的dataRetrievalService实例。
至于300秒后Future对象变为null的问题,大概率是任务抛出NPE后,线程池的异常处理导致Future状态异常;解决NPE问题后,这个现象应该也会随之消失。
具体修复步骤
1. 修改ProcessingThread类,通过构造函数传入依赖
去掉@Autowired注解,把DataRetrievalService作为构造参数传入,让Spring管理的依赖能正常传递到Callable实例中:
private class ProcessingThread implements Callable<List<Object>> { private final DataRetrievalService dataRetrievalService; String startDateTime; String endDateTime; int stationId; Set<String> channels; List<Integer> sensors; // 构造函数新增DataRetrievalService参数 public ProcessingThread(DataRetrievalService dataRetrievalService, String startDateTime, String endDateTime, int stationId, Set<String> channels, List<Integer> sensors) { super(); this.dataRetrievalService = dataRetrievalService; this.startDateTime = startDateTime; this.endDateTime = endDateTime; this.stationId = stationId; this.channels = channels; this.sensors = sensors; } @Override public List<Object> call() throws Exception { System.out.println("I was called!!"); return dataRetrievalService.getTimeSeriesData(stationId, channels, sensors, startDateTime, endDateTime); } }
2. 在调用方传入dataRetrievalService实例
你的调用方类本身应该是Spring管理的Bean(否则匿名类写法也无法正常获取dataRetrievalService),所以先在调用方类中注入该服务,再在创建ProcessingThread时传入:
// 调用方类中先注入DataRetrievalService @Autowired private DataRetrievalService dataRetrievalService; // 修改循环中的Callable创建逻辑 int noOfYears=1; for (int i = 0; i < noOfYears; i++) { if (i == 0) { endDateTime = dateTimeUtil.getOneYearLaterDate(startDateTime); } else { startDateTime = dateTimeUtil.getOneYearLaterDate(startDateTime); endDateTime = dateTimeUtil.getOneYearLaterDate(endDateTime); } // 传入已注入的dataRetrievalService Callable<List<Object>> callable = new ProcessingThread(dataRetrievalService, startDateTime, endDateTime, stationId, channels, sensors); futures.add(executorService.submit(callable)); } executorService.awaitTermination(300, TimeUnit.SECONDS); List<Object> b = new ArrayList<>(); // 提前初始化,避免空指针 int idx = 0; // 避免和循环变量i重名造成混淆 for (Future<List<Object>> future : futures) { if (idx == 0) { b = new ArrayList<>(future.get()); } else { b.addAll(future.get()); // 用addAll合并结果,而不是add把整个List作为单个元素加入 } idx++; }
额外优化提示
- 遍历Future时,
b.add(future.get())会把每个子List作为单个元素加入集合,改成b.addAll(future.get())才能正确合并所有数据。 - 提前初始化
b,避免当noOfYears为0时出现空指针问题。
为什么匿名类写法能运行?
匿名内部类定义在Spring管理的Bean内部,它可以访问外部类的成员变量(包括Spring注入的dataRetrievalService),相当于间接使用了Spring管理的实例。而手动new的ProcessingThread不属于Spring容器的管理范围,Spring不会为它注入任何依赖,所以@Autowired注解完全无效。
内容的提问来源于stack exchange,提问作者Sunny

