Spring Метод загрузки @Async в контроллере выполняется синхронно
My [basic] Spring Загрузочное приложение принимает запрос от браузера, отправленный через 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 для загрузки может быть загружено здесь. Если Maven установлен, проект можно запустить с помощью простой команды:
mvn clean spring-boot:run
Примечание Проблема была решена благодаря ответу, предоставленному ниже @Dave Syer, который указал, что мне не хватает @EnableAsync
в моем приложении, хотя у меня была строка в фрагменте кода выше.
Ответы
Ответ 1
Вы вызываете метод @Async
из другого метода в том же классе. Если вы не включили режим прокси-сервера AspectJ для @EnableAsync
(и, конечно же, обеспечиваете ткача), который не будет работать ( "self-invocation" прокси-сервера Google). Самое простое решение - поместить метод @Async
в другой @Bean
.
Ответ 2
Для всех тех, кто все еще ищет все шаги в @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
Ответ 3
У меня была аналогичная проблема, и у меня были аннотации @Async и @EnableAsync в правильном beans, и все же метод выполнялся синхронно. После того, как я проверил журналы, появилось предупреждение о том, что у меня было более одного bean типа ThreadPoolTaskExecutor, и ни один из них не назывался taskExecutor So...
@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 для конфигурации, доступной для пула потоков.
Ответ 4
Выполните три шага:
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("Asynchronous Process-");
executor.initialize();
return executor;
}
3 шаг: поместите @Async поверх намеченного метода
T