Весенний WebFlux – новый модуль, представленный в Spring 5. Spring WebFlux – это первый шаг к реактивной модели программирования в Spring Framework.
Реактивное программирование в Spring
Если вы новичок в реактивной модели программирования, то я настоятельно рекомендую вам ознакомиться с следующими статьями, чтобы узнать больше о реактивном программировании.
Если вы новичок в Spring 5, пожалуйста, прочитайте Особенности Spring 5.
Spring WebFlux
Spring WebFlux – это альтернативный модуль Spring MVC. Spring WebFlux используется для создания полностью асинхронных и неблокирующих приложений, построенных на модели выполнения событийных циклов. Ниже приведена диаграмма из официальной документации Spring, которая предоставляет отличное представление о сравнении Spring WebFlux с Spring Web MVC. Если вы собираетесь разрабатывать веб-приложение или веб-сервис REST в неблокирующей реактивной модели, то вы можете обратить внимание на Spring WebFlux. Spring WebFlux поддерживается на Tomcat, Jetty, контейнерах Servlet 3.1+ а также на контейнерах, не связанных с Servlet, таких как Netty и Undertow. Spring WebFlux построен на Project Reactor. Project Reactor – это реализация спецификации Reactive Streams. Reactor предоставляет два типа:
- Mono: реализует интерфейс Publisher и возвращает 0 или 1 элемент
- Flux: реализует интерфейс Publisher и возвращает N элементов.
Пример приветствия Spring WebFlux
Построим простое приложение Hello World на Spring WebFlux. Мы создадим простой веб-сервис REST и будем использовать Spring Boot для его запуска на сервере Netty по умолчанию. Структура нашего окончательного проекта выглядит следующим образом. Давайте рассмотрим каждый компонент приложения по очереди.
Зависимости Maven для Spring WebFlux
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.journaldev.spring</groupId>
<artifactId>SpringWebflux</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Spring WebFlux</name>
<description>Spring WebFlux Example</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<jdk.version>1.9</jdk.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>${jdk.version}</source>
<target>${jdk.version}</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
Наиболее важные зависимости – это spring-boot-starter-webflux
и spring-boot-starter-parent
. Некоторые другие зависимости нужны для создания модульных тестов JUnit.
Обработчик Spring WebFlux
Метод обработчика Spring WebFlux обрабатывает запрос и возвращает Mono
или Flux
в качестве ответа.
package com.journaldev.spring.component;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
@Component
public class HelloWorldHandler {
public Mono<ServerResponse> helloWorld(ServerRequest request) {
return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
.body(BodyInserters.fromObject("Hello World!"));
}
}
Обратите внимание, что реактивный компонент Mono
содержит тело ServerResponse
. Также обратите внимание на цепочку функций для установки типа возвращаемого содержимого, кода ответа и тела.
Маршрутизатор Spring WebFlux
Методы маршрутизатора используются для определения маршрутов для приложения. Эти методы возвращают объект RouterFunction
, который также содержит тело ServerResponse
.
package com.journaldev.spring.component;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
@Configuration
public class HelloWorldRouter {
@Bean
public RouterFunction<ServerResponse> routeHelloWorld(HelloWorldHandler helloWorldHandler) {
return RouterFunctions.route(RequestPredicates.GET("/helloWorld")
.and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), helloWorldHandler::helloWorld);
}
}
Поэтому мы предоставляем метод GET для /helloWorld
, и клиентский вызов должен принимать ответ в виде обычного текста.
Приложение Spring Boot
Давайте настроим наше простое веб-приложение WebFlux с помощью Spring Boot.
package com.journaldev.spring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Если вы посмотрите на приведенный выше код, там нет ничего, связанного с Spring WebFlux. Но Spring Boot настроит наше приложение как Spring WebFlux, поскольку мы добавили зависимость от модуля spring-boot-starter-webflux
.
Поддержка модулей Java 9
Наше приложение готово к выполнению на Java 8, но если вы используете Java 9, то также нужно добавить класс module-info.java
.
module com.journaldev.spring {
requires reactor.core;
requires spring.web;
requires spring.beans;
requires spring.context;
requires spring.webflux;
requires spring.boot;
requires spring.boot.autoconfigure;
exports com.journaldev.spring;
}
Запуск приложения Spring WebFlux Spring Boot
Если у вас есть поддержка Spring в Eclipse, то вы можете запустить вышеуказанный класс как приложение Spring Boot. Если вы предпочитаете использовать командную строку, то откройте терминал и выполните команду
mvn spring-boot:run
из директории исходного кода проекта. Как только приложение запущено, обратите внимание на следующие сообщения журнала, чтобы убедиться, что все в порядке с нашим приложением. Это также полезно, когда вы расширяете это простое приложение, добавляя больше маршрутов и функциональности.
2018-05-07 15:01:47.893 INFO 25158 --- [ main] o.s.w.r.f.s.s.RouterFunctionMapping : Mapped ((GET && /helloWorld) && Accept: [text/plain]) -> com.journaldev.spring.component.HelloWorldRouter$$Lambda$501/704766954@6eeb5d56
2018-05-07 15:01:48.495 INFO 25158 --- [ctor-http-nio-1] r.ipc.netty.tcp.BlockingNettyContext : Started HttpServer on /0:0:0:0:0:0:0:0:8080
2018-05-07 15:01:48.495 INFO 25158 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8080
2018-05-07 15:01:48.501 INFO 25158 --- [ main] com.journaldev.spring.Application : Started Application in 1.86 seconds (JVM running for 5.542)
Из журналов ясно, что наше приложение запущено на сервере Netty на порту 8080. Давайте перейдем к тестированию нашего приложения.
Тестирование приложения Spring WebFlux
Мы можем протестировать наше приложение с различными методами.
-
Используя команду CURL
$ curl https://localhost:8080/helloWorld Hello World! $
-
Откройте URL в браузере
-
Использование WebTestClient из Spring 5 Вот программа тестирования JUnit для тестирования нашего веб-сервиса Rest с использованием
WebTestClient
из реактивного веб-приложения Spring 5.package com.journaldev.spring; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.reactive.server.WebTestClient; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class SpringWebFluxTest { @Autowired private WebTestClient webTestClient; @Test public void testHelloWorld() { webTestClient .get().uri("/helloWorld") // GET-метод и URI .accept(MediaType.TEXT_PLAIN) // установка ACCEPT-Content .exchange() // предоставляет доступ к ответу .expectStatus().isOk() // проверка, является ли ответ OK .expectBody(String.class).isEqualTo("Hello World!"); // проверка типа ответа и сообщения } }
Запустите его как тестовый случай JUnit, и он должен успешно выполниться.
-
Используя WebClient из Spring Web Reactive, мы также можем использовать
WebClient
для вызова веб-службы REST.package com.journaldev.spring.client; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; public class HelloWorldWebClient { public static void main(String args[]) { WebClient client = WebClient.create("https://localhost:8080"); Mono<ClientResponse> result = client.get() .uri("/helloWorld") .accept(MediaType.TEXT_PLAIN) .exchange(); System.out.println("Result = " + result.flatMap(res -> res.bodyToMono(String.class)).block()); } }
Просто запустите его как обычное приложение на Java, и вы увидите правильный вывод с большим количеством отладочных сообщений.
Сводка
В этом посте мы узнали о Spring WebFlux и о том, как создать простой реактивный веб-сервис hello world. Приятно видеть, что популярные фреймворки, такие как Spring, поддерживают реактивную модель программирования. Но у нас есть еще много вопросов, потому что, если все ваши зависимости не являются реактивными и не блокирующими, то и ваше приложение тоже не является по-настоящему реактивным. Например, у поставщиков реляционных баз данных нет реактивных драйверов, потому что они зависят от JDBC, который не является реактивным. Следовательно, API Hibernate также не является реактивным. Итак, если вы используете реляционные базы данных, то вы пока не можете создать по-настоящему реактивное приложение. Я надеюсь, что это изменится скоро.
Вы можете скачать код проекта с моего репозитория GitHub.
Ссылка: Официальная документация
Source:
https://www.digitalocean.com/community/tutorials/spring-webflux-reactive-programming