Spring Начальная обработка контроллера после отправки ответа

Я использую контроллер Spring MVC и хочу начать выполнение задачи в новом потоке. Однако задача должна начинаться не сразу, а только после того, как ответ будет отправлен клиенту.

Последовательность - в строгом временном порядке:

  1. запрос
  2. вернуть новый ResponseEntity.../клиент получает статус HTTP 200 OK.
  3. обработка задания начинается.

Как мне этого добиться?

Я хотел использовать асинхронную абстракцию Spring, вызывая метод, аннотированный @Async, но он не гарантирует, что новый поток будет ожидать отправки ответа первым.

Ответ 1

Вы можете использовать перехватчик для этого. Порядок событий для обработки запроса в Spring MVC:

  • DispatcherServlet получает пару Request, Response и определяет обработку
  • [опционально] вызывается preHandle перехватчиков (с возможностью остановки обработки)
  • контроллер называется
  • [опционально] называются перехватчики postHandle
  • ViewResolver и view выполняют фактическую обработку ответа и отправляют ответ
  • [опционально] называются перехватчики afterCompletion

afterCompletion слишком упрощено и предназначено только для того, чтобы показать, что методы afterCompletion перехватчика afterCompletion после отправки ответа клиенту со следующей сигнатурой:

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

В этом методе вы можете проверить возникновение исключения и правильность ответа (ex == null && response.getStatus() == HttpServletResponse.SC_OK) перед началом обработки.

Ответ 2

Если ваше требование "после ответа отправлено" выполнено с "после того, как представление было обработано", вы можете использовать реализацию HandlerInterceptor. Для примера ср. Учебник Spring 3 MVC Interceptor с примером запуска вашей работы в 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

Вы можете добавить задачу в очередь блокировки, прежде чем объект ответа будет создан. Пусть исполнитель задачи запускается периодически (каждые х секунд) по очереди и опроса для задач. Если задача найдена, она будет выполнена. Если нет, поток заканчивает свой метод запуска и ждет следующего прогона (через х секунд).

Как периодически запускать задачу: http://www.mkyong.com/java/how-to-run-a-task-periodically-in-java/

Ввести очередь как зависимость как в контроллере, так и в службе исполнителя задачи. Это должно быть легкое решение для начала.

В этом szenario вы не можете быть уверены, что клиент получает запрос. Но если вы хотите быть в безопасности (r), добавьте дату выполнения к объекту задачи с достаточным смещением (например, текущее время + 30 секунд). Пусть исполнитель задачи проверяет, соответствует ли дата выполнения опроса заданной или прошлой. В противном случае игнорировать задачу для этого прогона.