【问题标题】:Spring Controller start processing after response is sent发送响应后 Spring Controller 开始处理
【发布时间】:2014-12-16 15:32:54
【问题描述】:

我正在使用 Spring MVC 控制器并希望在新线程中开始执行任务。但是,该任务不应立即开始,而应在响应发送到客户端之后才开始。

顺序 - 严格的时间顺序:

  1. 请求
  2. return new ResponseEntity ... / 客户端收到 HTTP 状态 200 OK。
  3. 开始处理任务。

我如何实现这一目标?

我想通过调用带有@Async注解的方法来使用Spring的异步抽象,但是它不能保证新线程会等待响应首先发送。

【问题讨论】:

  • 你能告诉我们你到目前为止做了什么吗?

标签: java spring-mvc concurrency


【解决方案1】:

您可以为此使用拦截器。 Spring MVC中处理请求的事件顺序为:

  • DispatcherServlet 获取请求、响应对并确定处理方式
  • [可选] 拦截器 preHandle 被调用(带有停止处理的选项)
  • 控制器被调用
  • [可选] 拦截器 postHandle 被调用
  • ViewResolver 和视图执行实际的响应处理并发送响应
  • [可选] 在完成后调用拦截器

以上内容过于简化,只是为了显示拦截器afterCompletion方法在响应发送到客户端后被调用,具有以下签名:

void afterCompletion(HttpServletRequest request,
                     HttpServletResponse response,
                     Object handler,
                     Exception ex)
                     throws Exception

在该方法中,您可以在开始处理之前测试异常的发生和响应的正确性 (ex == null && response.getStatus() == HttpServletResponse.SC_OK)。

【讨论】:

  • @Hille 已经给出了这个想法,但我正在准备这个答案,只是在发布后才看到。
  • @Hille:你确实值得拥有!
  • 拦截器的 afterCompletion() 甚至在响应发送之前执行。我有什么明显的遗漏吗?
【解决方案2】:

如果您的“发送响应后”要求通过“呈现视图后”得到满足,您可以使用HandlerInterceptor 的实现。例如cf。 Spring 3 MVC Interceptor tutorial with example,在afterCompletion 中触发你的工作。

如果您的工作需要“在触线后”触发,我想知道原因。

【讨论】:

    【解决方案3】:

    HandlerInterceptor 是解决方案,但代码比预期的要复杂一些。

    这里有一个代码建议,通过将整个解决方案放在一个类中来使其更简单:

    private static final ThreadLocal<Object> result = new ThreadLocal<Object>();
    
    @RequestMapping("/mypath")
    public Object execute() throws Exception {
        Object obj = new Object();
        result.set(obj); // Save the object to be used after response
        return obj;
    }
    
    @Bean
    public MappedInterceptor interceptor() {
        return new MappedInterceptor(Arrays.array("/mypath"), new HandlerInterceptor() {
            @Override
            public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
                // Get the saved object
                Object results = result.get();
    
                // Clean for the next request
                result.set(null);
    
                // TODO Your code to be executed after response.
            }
        });
    }
    

    【讨论】:

      【解决方案4】:

      您可以在创建响应实体之前将任务添加到阻塞队列。让任务执行器定期(每 x 秒)在队列上运行并轮询任务。如果找到一个任务,它将被执行。如果不是,则线程完成其 run 方法并等待下一次运行(在 x 秒内)。

      如何定期运行任务: http://www.mkyong.com/java/how-to-run-a-task-periodically-in-java/

      在控制器和任务执行器服务中注入队列作为依赖项。这应该是一个简单的解决方案。

      在这种情况下,您不能确定客户端是否收到了请求。但是,如果您想安全(r),请为您的任务对象添加一个具有足够偏移量的截止日期(例如当前时间 + 30 秒)。让任务执行者检查轮询任务的到期日期是现在还是过去。否则忽略此运行的任务。

      【讨论】:

        猜你喜欢
        • 2011-09-08
        • 1970-01-01
        • 2022-01-20
        • 1970-01-01
        • 1970-01-01
        • 2021-04-27
        • 1970-01-01
        • 2011-02-17
        • 2021-02-02
        相关资源
        最近更新 更多