В этой статье мы рассмотрим поддержку асинхронного выполнения в Spring или Spring Boot с использованием @Async
аннотации Spring .
Мы будем аннотировать метод бобов; @Async
заставит его выполняться в отдельном потоке, т.е. вызывающая сторона не будет ждать завершения вызванного метода.
Если вы уже работали над приложением Spring или Spring Boot и у вас есть требование использовать его в качестве асинхронного механизма, то эти три быстрых шага помогут вам начать работу.
Шаг 1. Включите асинхронную поддержку
Давайте начнем путем включения асинхронной обработки с конфигурацией Java , просто добавив @EnableAsync
к классу конфигурации: аннотацию переключает способность Spring для запуска методов в пуле фон потоков.@EnableAsync
@Async
Шаг 2. Добавьте аннотацию @Async к методу
Убедитесь, что метод, который мы аннотируем, @Async
должен быть публичным, чтобы его можно было проксировать. И самовывоз не работает, потому что он обходит прокси и напрямую вызывает базовый метод.
Шаг 3: Исполнитель (настройка по умолчанию)
Давайте настроим ThreadPoolTaskExecutor
. В нашем случае мы хотим ограничить число одновременных потоков до двух и ограничить размер очереди до 500. Есть еще много вещей, которые вы можете настроить. По умолчанию SimpleAsyncTaskExecutor
используется.
@Bean("threadPoolTaskExecutor")
public TaskExecutor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(1000);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setThreadNamePrefix("Async-");
return executor;
}
Вот и все, это три быстрых шага, которые помогут вам создать асинхронные сервисы с помощью Spring или Spring Boot. Давайте разработаем полный пример, чтобы продемонстрировать, как мы можем создавать асинхронные сервисы, используя Spring или Spring Boot.
Учитесь и осваивайте Spring Boot в
учебнике Spring Boot
Что мы будем строить
Мы создадим сервис поиска, который запрашивает информацию о пользователях GitHub и получает данные через API GitHub. Один из подходов к масштабированию сервисов — запуск дорогих заданий в фоновом режиме и ожидание результатов с использованием CompletableFuture
интерфейса Java . Ява CompletableFuture
— это эволюция обычного Future
. Это облегчает конвейеризацию нескольких асинхронных операций, объединяя их в одно асинхронное вычисление.
Используемые инструменты и технологии
- Spring Boot — 2.0.6.RELEASE
- JDK — 1,8 или позже
- Spring Framework — 5.0.9 РЕЛИЗ
- Maven — 3.2+
- IDE — Eclipse или Spring Tool Suite (STS)
Создать и импортировать Spring Boot Project
Существует много способов создания приложения Spring Boot. Самый простой способ — использовать Spring Initializr по адресу http://start.spring.io/ , который является генератором онлайн-приложений Spring Boot. Посмотрите на приведенную выше диаграмму, мы указали следующие детали:
- Генерация: Maven Project
- Версия Java: 1.8 (по умолчанию)
- Spring Boot: 2.0.4
- Группа: net.javaguides.springboot
- Артефакт: springboot-async-example
- Имя: springboot-async-example
- Описание: Демонстрационный проект для Spring Boot
- Имя пакета: net.guides.springboot.springbootasyncexample
- Упаковка: банка (это значение по умолчанию)
- Зависимости: Интернет
После того, как все детали введены, нажмите кнопку «Создать проект». Он сгенерирует проект Spring Boot и загрузит его. Затем распакуйте загруженный zip-файл и импортируйте его в свою любимую среду IDE.
Структура каталога проекта
Ниже на диаграмме показана структура проекта для справки:
Файл pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.guides.springboot</groupId>
<artifactId>springboot-async-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springboot-async-example</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Создать представление пользователя GitHub
Давайте создадим класс модели GitHub User с полями name и blog.
package net.guides.springboot.springbootasyncexample.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class User {
private String name;
private String blog;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBlog() {
return blog;
}
public void setBlog(String blog) {
this.blog = blog;
}
@Override
public String toString() {
return "User [name=" + name + ", blog=" + blog + "]";
}
}
Обратите внимание, что Spring использует библиотеку JSON Джексона для преобразования ответа JSON GitHub в объект User. В @JsonIgnoreProperties
аннотации сигналы Spring игнорировать любые атрибуты , не перечисленные в классе. Это позволяет легко выполнять вызовы REST и создавать доменные объекты. В этой статье мы используем только название и URL блога для демонстрационных целей.
Создайте службу поиска GitHub
Далее нам нужно создать сервис, который запрашивает GitHub для поиска информации о пользователе.
package net.guides.springboot.springbootasyncexample.service;
import java.util.concurrent.CompletableFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import net.guides.springboot.springbootasyncexample.model.User;
@Service
public class GitHubLookupService {
private static final Logger logger = LoggerFactory.getLogger(GitHubLookupService.class);
private final RestTemplate restTemplate;
public GitHubLookupService(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}
@Async("threadPoolTaskExecutor")
public CompletableFuture < User > findUser(String user) throws InterruptedException {
logger.info("Looking up " + user);
String url = String.format("https://api.github.com/users/%s", user);
User results = restTemplate.getForObject(url, User.class);
// Artificial delay of 1s for demonstration purposes
Thread.sleep(1000 L);
return CompletableFuture.completedFuture(results);
}
}
GitHubLookupService
Класс использует Spring , RestTemplate
чтобы вызвать удаленную точку REST (api.github.com/users/) , а затем преобразовать ответ в объект пользователя. Spring Boot автоматически предоставляет, RestTemplateBuilder
который настраивает значения по умолчанию с любыми битами автоконфигурации (то есть MessageConverter
). findUser
Метод помечается Спринг @Async
аннотации, указав , что он будет работать в отдельном потоке. Метод в возвращаемом типе CompletableFuture
, а User
, требование для любой асинхронной службы. Этот код использует completedFuture
метод для возврата CompletableFuture
экземпляра, который уже завершен результатом запроса GitHub.
Сделайте приложение исполняемым
Чтобы запустить образец, вы можете создать исполняемый файл jar. Давайте используем это, CommandLineRunner
чтобы внедрить GitHubLookupService
и вызывает этот сервис четыре раза, чтобы продемонстрировать выполнение метода асинхронно
package net.guides.springboot.springbootasyncexample;
import java.util.concurrent.CompletableFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import net.guides.springboot.springbootasyncexample.model.User;
import net.guides.springboot.springbootasyncexample.service.GitHubLookupService;
@SpringBootApplication
@EnableAsync
public class SpringbootAsyncApplication implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(SpringbootAsyncApplication.class);
@Autowired
private GitHubLookupService gitHubLookupService;
@Bean("threadPoolTaskExecutor")
public TaskExecutor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(1000);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setThreadNamePrefix("Async-");
return executor;
}
public static void main(String[] args) {
SpringApplication.run(SpringbootAsyncApplication.class, args);
}
@Override
public void run(String...args) throws Exception {
// Start the clock
long start = System.currentTimeMillis();
// Kick of multiple, asynchronous lookups
CompletableFuture < User > page1 = gitHubLookupService.findUser("PivotalSoftware");
CompletableFuture < User > page2 = gitHubLookupService.findUser("CloudFoundry");
CompletableFuture < User > page3 = gitHubLookupService.findUser("Spring-Projects");
CompletableFuture < User > page4 = gitHubLookupService.findUser("RameshMF");
// Wait until they are all done
CompletableFuture.allOf(page1, page2, page3, page4).join();
// Print results, including elapsed time
logger.info("Elapsed time: " + (System.currentTimeMillis() - start));
logger.info("--> " + page1.get());
logger.info("--> " + page2.get());
logger.info("--> " + page3.get());
logger.info("--> " + page4.get());
}
}
@EnableAsync
Аннотацию переключается на способность Spring , чтобы запустить @Async
методы в пуле фон потоков. Этот класс также настраивает используемый Executor
. В нашем случае мы хотим ограничить число одновременных потоков до двух и ограничить размер очереди до 500. Есть еще много вещей, которые вы можете настроить. По умолчанию SimpleAsyncTaskExecutor
используется.
Запущенное приложение
Есть два способа запустить автономное приложение Spring Boot.
- Мы используем Maven для запуска приложения, используя ./mvnw spring-boot: run. Или вы можете собрать JAR-файл с помощью чистого пакета ./mvnw. Затем вы можете запустить файл JAR:
java -jar target/springboot-async-example.jar
- На диаграмме ниже показано, как запустить приложение Spring Boot из среды IDE — щелкните правой кнопкой мыши и запустите
SpringbootAsyncApplication.main()
метод как отдельный класс Java.
Вывод
Когда мы запустим приложение, мы увидим следующий вывод:
Учитесь и осваивайте Spring Boot в
учебнике Spring Boot
Исходный код этой статьи доступен в моем репозитории GitHub .