You need to enable JavaScript to run this app. Rest Controller返回响应后异步写文件,@Async不生效该如何实现?
优惠活动
大模型
产品
解决方案
定价
更多

Rest Controller返回响应后异步写文件,@Async不生效该如何实现?

问题原因排查(@Async不生效的常见场景)

  • 没有在启动类/配置类上加@EnableAsync注解,这是Spring异步功能生效的前置条件
  • 写文件的异步方法和调用它的Controller方法定义在同一个类中,AOP切面无法生效,相当于直接调用普通方法自然会同步阻塞
  • 异步方法的返回值不是void或者Future类型,Spring会同步等待返回结果
  • 异步线程池配置的拒绝策略是CallerRunsPolicy,异步任务被拒绝后直接在调用线程执行也会导致阻塞

可行实现方案

方案1:修复@Async配置,正确使用异步方法

  1. 启动类添加@EnableAsync注解开启异步能力
@SpringBootApplication
@EnableAsync
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
  1. 将异步写文件的逻辑单独抽为独立Bean,不要和Controller写在同一个类中
@Component
public class ResponseLogWriter {
    @Async
    public void writeResponseToFile(String responseContent, String uri, Integer statusCode) {
        // 自定义写文件逻辑
    }
}
  1. 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事件实现解耦

该方案耦合度更低,更适合日志这类非核心旁路逻辑

  1. 定义响应日志事件类
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方法
}
  1. 定义异步事件监听器
@Component
public class ResponseLogListener {
    @Async
    @EventListener(ResponseLogEvent.class)
    public void handleLogEvent(ResponseLogEvent event) {
        // 写文件逻辑
    }
}
  1. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.19 18:54:02