Rest Controller返回响应后异步写文件,@Async不生效该如何实现?
问题原因排查(@Async不生效的常见场景)
- 没有在启动类/配置类上加
@EnableAsync注解,这是Spring异步功能生效的前置条件 - 写文件的异步方法和调用它的Controller方法定义在同一个类中,AOP切面无法生效,相当于直接调用普通方法自然会同步阻塞
- 异步方法的返回值不是
void或者Future类型,Spring会同步等待返回结果 - 异步线程池配置的拒绝策略是
CallerRunsPolicy,异步任务被拒绝后直接在调用线程执行也会导致阻塞
可行实现方案
方案1:修复@Async配置,正确使用异步方法
- 启动类添加
@EnableAsync注解开启异步能力
@SpringBootApplication @EnableAsync public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
- 将异步写文件的逻辑单独抽为独立Bean,不要和Controller写在同一个类中
@Component public class ResponseLogWriter { @Async public void writeResponseToFile(String responseContent, String uri, Integer statusCode) { // 自定义写文件逻辑 } }
- Controller中注入该Bean,返回响应前调用异步方法即可
@RestController public class TestController { @Autowired private ResponseLogWriter logWriter; @GetMapping("/test") public String testApi() { String response = "接口返回内容"; // 异步调用,不会阻塞主线程 logWriter.writeResponseToFile(response, "/test", 200); return response; } }
建议自定义@Async绑定的专属线程池,避免异步任务占用默认Tomcat业务线程
方案2:基于Spring ApplicationEvent事件实现解耦
该方案耦合度更低,更适合日志这类非核心旁路逻辑
- 定义响应日志事件类
public class ResponseLogEvent extends ApplicationEvent { private String responseContent; private String uri; private Integer statusCode; public ResponseLogEvent(Object source, String responseContent, String uri, Integer statusCode) { super(source); this.responseContent = responseContent; this.uri = uri; this.statusCode = statusCode; } // 省略getter方法 }
- 定义异步事件监听器
@Component public class ResponseLogListener { @Async @EventListener(ResponseLogEvent.class) public void handleLogEvent(ResponseLogEvent event) { // 写文件逻辑 } }
- Controller中发布事件即可
@RestController public class TestController { @Autowired private ApplicationEventPublisher eventPublisher; @GetMapping("/test") public String testApi() { String response = "接口返回内容"; eventPublisher.publishEvent(new ResponseLogEvent(this, response, "/test", 200)); return response; } }
方案3:全局拦截统一处理(无需修改Controller代码)
如果需要对所有接口生效,可以用ResponseBodyAdvice全局拦截响应内容统一处理:
@ControllerAdvice public class GlobalResponseHandler implements ResponseBodyAdvice<Object> { @Autowired private ResponseLogWriter logWriter; @Override public boolean supports(MethodParameter returnType, Class converterType) { // 配置需要拦截的接口规则,返回true表示全部拦截 return true; } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { // 异步写日志 logWriter.writeResponseToFile(JSON.toJSONString(body), request.getURI().getPath(), response.getStatusCode().value()); return body; } }
内容的提问来源于stack exchange,提问作者Dams
相关产品推荐
相关产品推荐

