Spring WebFlux – תכנות ריאקטיבי של Spring

הַ- Spring WebFlux הִוא הַמוֹדוּל הַחָדָשׁ שֶׁהוּכְנִיסוּ בְּ- Spring 5. הַ- Spring WebFlux הִיא הַצָּעָד הָרִאשׁוֹן לְעַבוֹדָה רְאַקְטִיבִית בְּמוֹדֶל תְּכֵנָה שֶׁל Spring.

תְּכֵנָה רְאַקְטִיבִית בְּ- 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 מספק שני סוגים:

  1. Mono: מיישם Publisher ומחזיר 0 או 1 אלמנטים
  2. Flux: מיישם Publisher ומחזיר N אלמנטים.

דוגמה לשלום עולם ב-Spring WebFlux

בואו נבנה יישום פשוט של Spring WebFlux Hello World. ניצור שירות 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. כך שאנחנו מחשפים שיטת GET לכתובת /helloWorld וקריאת הלקוח אמורה לקבל תגובת טקסט פשוטה.

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);
	}
}

יישום 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

אנחנו יכולים לבדוק את היישום שלנו באמצעות שיטות שונות.

  1. שימוש בפקודת CURL

    $ curl https://localhost:8080/helloWorld
    Hello World!
    $ 
    
  2. פתיחת כתובת URL בדפדפן

  3. שימוש ב-WebTestClient מ־Spring 5 זהו תוכנית מבחן JUnit לבדיקת השירות האינטרנט שלנו באמצעות 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 method and URI
    		.accept(MediaType.TEXT_PLAIN) //setting ACCEPT-Content
    		.exchange() //gives access to response
    		.expectStatus().isOk() //checking if response is OK
    		.expectBody(String.class).isEqualTo("Hello World!"); // checking for response type and message
    	}
    
    }
    

    הרץ אותה כמבחן JUnit והוא צריך לעבור בצבעים זוהרים.

  4. שימוש ב-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());
    	}
    	
    }
    

    רק הריצו אותו כיישום ג'אווה פשוט ואתם צריכים לראות את הפלט המתאים עם הודעות דיבוג רבות.

סיכום

בפוסט זה למדנו על Spring WebFlux וכיצד לבנות שירות רשת Restful ריאקטיבי "hello world". זה מרגש לראות שגם ספריות פופולריות כמו Spring תומכות במודל תכנות ריאקטיבי. אך יש לנו הרבה לכסות מכיוון שאם כל התלויותיך אינן ריאקטיביות ובלתי חוסמות, אז היישום שלך אינו רקטיבי באמת. לדוגמה, ספקי מסדי נתונים יחסיים אינם מספקים דרייברים ריאקטיביים מאחר והם תלויים ב-JDBC, שאינו ריאקטיבי. לכן, גם ממשק ה-Hibernate הוא אינו ריאקטיבי. לכן, אם אתה משתמש במסדי נתונים יחסיים, אתה לא יכול לבנות אפליקציה באמת ריאקטיבית, לפחות כרגע. אני מקווה שזה ישתנה מהר מאוד.

ניתן להוריד את קוד הפרויקט מה מאגר הקוד שלי ב-GitHub.

התייחסות: תיעוד רשמי

Source:
https://www.digitalocean.com/community/tutorials/spring-webflux-reactive-programming