Using Callable and DeferredResult for Asynchronous Request Handling in Spring MVC
This article demonstrates how to implement asynchronous request processing in Spring MVC using Callable and DeferredResult, including setup of a base module with ResponseMsg, creating blocking and async controllers, managing task queues, and handling timeouts and completions with practical code examples.
Spring MVC provides Callable and DeferredResult to handle long‑running requests asynchronously. When a controller returns one of these types, the servlet thread is released, allowing the server to serve other requests while the result is produced in a separate thread.
Base module preparation : a generic ResponseMsg<T> class is defined to standardize API responses, using Lombok annotations ( @Data , @NoArgsConstructor , @AllArgsConstructor ) to generate boilerplate code.
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResponseMsg
{
private int code;
private String msg;
private T data;
}TaskService supplies the actual business logic, simulating a 30‑second operation and returning a ResponseMsg<String> instance.
@Service
public class TaskService {
private static final Logger log = LoggerFactory.getLogger(TaskService.class);
public ResponseMsg
getResult() {
log.info("任务开始执行,持续等待中...");
try { Thread.sleep(30000L); } catch (InterruptedException e) { e.printStackTrace(); }
log.info("任务处理完成");
return new ResponseMsg<>(0, "操作成功", "success");
}
}Blocking call : a traditional controller returns ResponseMsg directly, causing the servlet thread to block for the full duration.
@RestController
public class BlockController {
@Autowired private TaskService taskService;
@RequestMapping(value="/get", method=RequestMethod.GET)
public ResponseMsg
getResult() {
log.info("接收请求,开始处理...");
ResponseMsg
result = taskService.getResult();
log.info("接收任务线程完成并退出");
return result;
}
}Callable async call : the controller returns a Callable<ResponseMsg<String>> . The servlet thread is released immediately, while Spring executes the Callable in a separate thread.
@RestController
public class TaskController {
@Autowired private TaskService taskService;
@RequestMapping(value="/get", method=RequestMethod.GET)
public Callable
> getResult() {
log.info("接收请求,开始处理...");
Callable
> result = () -> taskService.getResult();
log.info("接收任务线程完成并退出");
return result;
}
}DeferredResult async call : a DeferredResult<ResponseMsg<String>> is created with a timeout and a default timeout response. The result is stored in a custom TaskQueue and later completed by a background thread.
@RestController
public class TaskController {
private static final ResponseMsg
OUT_OF_TIME_RESULT = new ResponseMsg<>(-1, "超时", "out of time");
private static final long OUT_OF_TIME = 3000L;
@Autowired private TaskQueue taskQueue;
@RequestMapping(value="/get", method=RequestMethod.GET)
public DeferredResult
> getResult() {
log.info("接收请求,开始处理...");
DeferredResult
> result = new DeferredResult<>(OUT_OF_TIME, OUT_OF_TIME_RESULT);
result.onTimeout(() -> log.info("调用超时"));
result.onCompletion(() -> log.info("调用完成"));
synchronized (taskQueue) { taskQueue.put(result); }
log.info("接收任务线程完成并退出");
return result;
}
}The TaskQueue holds a bounded LinkedBlockingDeque<Task> where each Task contains an ID and a DeferredResult . A TaskExecute component starts a background thread ( @PostConstruct ) that continuously polls the queue, generates a random string, sets it as the result, and sleeps for a random interval to simulate processing time.
@Component
public class TaskExecute {
@Autowired private TaskQueue taskQueue;
@PostConstruct public void init() { new Thread(this::execute).start(); }
private void execute() {
while (true) {
try {
Task task;
synchronized (taskQueue) { task = taskQueue.take(); }
if (task != null) {
String randomStr = getRandomStr(10);
ResponseMsg
response = new ResponseMsg<>(0, "success", randomStr);
task.getTaskResult().setResult(response);
}
Thread.sleep(random.nextInt(10) * 1000L);
} catch (InterruptedException e) { e.printStackTrace(); }
}
}
// getRandomStr implementation omitted for brevity
}Two practical scenarios are presented:
Scenario 1 : a client calls /get , receives a DeferredResult , which is queued; the background executor later fulfills the result with a random string.
Scenario 2 : multiple clients submit /get requests that are stored in a TaskSet . Another endpoint /set/{result} iterates over the set and sets the same result for all pending requests, demonstrating broadcast‑style completion.
Both scenarios include timeout handling (removing the deferred result from the collection) and completion callbacks that log the removal. The article concludes with a call‑to‑action for readers to follow the author’s public account and consider joining a knowledge community.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.