it-roy-ru.com

Spring Boot @Async метод в контроллере выполняется синхронно

Мое [основное] приложение Spring Boot принимает запрос от браузера, отправленный через jQuery.get(), и должен немедленно получить ответ, такой как «ваш запрос был поставлен в очередь». Для этого я написал контроллер:

@Controller
public class DoSomeWorkController {

  @Autowired
  private final DoWorkService workService;

  @RequestMapping("/doSomeWork")
  @ResponseBody
  public String doSomeWork() {

    workService.doWork(); // time consuming operation
    return "Your request has been queued.";
  }
}

Класс DoWorkServiceImpl реализует интерфейс DoWorkService и действительно прост. У него есть единственный метод для выполнения трудоемкой задачи. Мне не нужно ничего возвращать из этого сервисного звонка, так как электронное письмо будет доставлено в конце работы, как в случае неудачи, так и в случае успеха. Так что это будет выглядеть примерно так:

@Service
public class DoWorkServiceImpl implements DoWorkService {

  @Async("workExecutor")
  @Override
  public void doWork() {

    try {
        Thread.sleep(10 * 1000);
        System.out.println("completed work, sent email");
    }
    catch (InterruptedException ie) {
        System.err.println(ie.getMessage());
    }
  }
}

Я думал, что это будет работать, но Ajax-запрос браузера ждал 10 секунд, прежде чем вернуть ответ. Таким образом, казалось бы, метод сопоставления контроллера вызывает внутренний метод, аннотированный с @Async синхронно. В традиционном приложении Spring я обычно добавляю это в конфигурацию XML:

<task:annotation-driven />
<task:executor id="workExecutor" pool-size="1" queue-capacity="0" rejection-policy="DISCARD" />

Поэтому я подумал, что написание эквивалента этого в основном классе приложения поможет:

@SpringBootApplication
@EnableAsync
public class Application {

  @Value("${pool.size:1}")
  private int poolSize;;

  @Value("${queue.capacity:0}")
  private int queueCapacity;

  @Bean(name="workExecutor")
  public TaskExecutor taskExecutor() {
      ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
      taskExecutor.setMaxPoolSize(poolSize);
      taskExecutor.setQueueCapacity(queueCapacity);
      taskExecutor.afterPropertiesSet();
      return taskExecutor;
  }

  public static void main(String[] args) {
      SpringApplication.run(Application.class, args);
  }
}

Это не изменило поведение. Ответ Ajax по-прежнему приходит через 10 секунд после отправки запроса. Что мне не хватает?

Приложение Spring Boot можно скачать здесь . При установленном Maven проект можно запустить с помощью простой команды:

mvn clean spring-boot:run

Note Эта проблема была решена благодаря ответу @Dave Syer ниже, который указал, что я пропустил @EnableAsync в своем приложении, хотя у меня была строка в фрагменте кода выше.

20
Web User

Вы вызываете метод @Async из другого метода того же класса. Если вы не включите режим прокси-сервера AspectJ для @EnableAsync (и, конечно, предоставите ткача), он не будет работать (google "самовывоз прокси"). Самое простое решение - поместить метод @Async в другой @Bean.

37
Dave Syer

Для всех тех, кто все еще ищет все шаги в @Asnyc, объясненные простым способом, вот ответ:

Вот простой пример с @Async. Чтобы заставить @Async работать в приложении Spring Boot, выполните следующие действия:

Шаг 1. Добавьте аннотацию @EnableAsync и добавьте компонент TaskExecutor в класс приложения.

Пример:

@SpringBootApplication
@EnableAsync
public class AsynchronousSpringBootApplication {

    private static final Logger logger = LoggerFactory.getLogger(AsynchronousSpringBootApplication.class);

    @Bean(name="processExecutor")
    public TaskExecutor workExecutor() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setThreadNamePrefix("Async-");
        threadPoolTaskExecutor.setCorePoolSize(3);
        threadPoolTaskExecutor.setMaxPoolSize(3);
        threadPoolTaskExecutor.setQueueCapacity(600);
        threadPoolTaskExecutor.afterPropertiesSet();
        logger.info("ThreadPoolTaskExecutor set");
        return threadPoolTaskExecutor;
    }

    public static void main(String[] args) throws Exception {
  SpringApplication.run(AsynchronousSpringBootApplication.class,args);
 }
}

Шаг 2: Добавить метод, который выполняет асинхронный процесс

@Service
public class ProcessServiceImpl implements ProcessService {

    private static final Logger logger = LoggerFactory.getLogger(ProcessServiceImpl.class);

    @Async("processExecutor")
    @Override
    public void process() {
        logger.info("Received request to process in ProcessServiceImpl.process()");
        try {
            Thread.sleep(15 * 1000);
            logger.info("Processing complete");
        }
        catch (InterruptedException ie) {
            logger.error("Error in ProcessServiceImpl.process(): {}", ie.getMessage());
        }
    }
}

Шаг 3. Добавьте API в контроллер для выполнения асинхронной обработки.

@Autowired
private ProcessService processService;

@RequestMapping(value = "ping/async", method = RequestMethod.GET)
    public ResponseEntity<Map<String, String>> async() {
        processService.process();
        Map<String, String> response = new HashMap<>();
        response.put("message", "Request is under process");
        return new ResponseEntity<>(response, HttpStatus.OK);
    }

Я также написал блог и рабочее приложение на GitHub с этими шагами. Пожалуйста, проверьте: http://softwaredevelopercentral.blogspot.com/2017/07/asynchronous-processing-async-in-spring.html

9
Aj Tech Developer

У меня была похожая проблема, и у меня были аннотации @Async и @EnableAsync в правильных bean-компонентах, и все же метод выполнялся синхронно. После того, как я проверил журналы, появилось предупреждение о том, что у меня есть более одного компонента типа ThreadPoolTaskExecutor, и ни один из них не называется taskExecutor Итак ...

@Bean(name="taskExecutor")
public ThreadPoolTaskExecutor defaultTaskExecutor() {
     ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
     //Thread pool configuration
     //...
     return pool;
}

См. http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.html для конфигурации, доступной для пула потоков.

3
Carlos Andres

Выполните три шага:

1 шаг: Использовать @EnableAsync с @configuration или @SpringBootApplication

@EnableAsync Public Class Application {

2 шаг:

/**
 * THIS FOR ASYNCRONOUS PROCESS/METHOD
 * @return
 */
@Bean
public Executor asyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(5);
    executor.setMaxPoolSize(5);
    executor.setQueueCapacity(500);
    executor.setThreadNamePrefix("Anycronous Process-");
    executor.initialize();
    return executor;
}

3 шаг: поместите @Async поверх намеченного метода

T

0
Sanjeet Shrivastava