Статьи

Spring Boot: создание асинхронных методов с использованием аннотации @Async

В этой статье мы рассмотрим поддержку асинхронного выполнения в 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 .

Ссылки