You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:06:31