Bộ sưu tập

Giới thiệu về Spring Webflux


https://codersontrang.com/2017/10/23/gioi-thieu-ve-spring-webflux/
Như trong bài viết trước, ta được biết rằng Spring Webflux là một framework cho việc xây dựng các ứng dụng web trên nền tảng Java, cũng là một sự lựa chọn khác hơn, mới hơn cho các lập trình viên Java web bên cạnh nền tảng công cụ truyền thống nổi tiếng là Spring MVC. Bài viết này xin phép được bổ sung thêm các thông tin về Webflux với ví dụ cụ thể, từ đó cùng với bài viết trước hi vọng bạn đọc có thể hiểu hơn và có cái nhìn mở hơn trong việc tiếp cận và ứng dụng framework mới này.

Spring Webflux là một trong những công cụ phát triển mới được ra đời từ Spring 5, vì vậy để có thể ứng dụng được công cụ này bạn phải đảm bảo rằng bạn đã sẵn sàng sử dụng Spring 5 và nền tảng JDK 8 trở lên. Cũng như sự tổng hợp lại một số chính ở trong bài viết trước, ta cùng nhau có một cái nhìn tổng quan lại về Spring Webflux cùng với người anh ruột của nó là Spring MVC theo sơ đồ dưới đây

Sơ đồ trên được phân chia theo từng lớp, đi từ lớp dưới nhất lên đến lớp trên cùng nhất, ta sẽ có được ứng dụng web hoàn chỉnh. Ta sẽ giải thích chi tiết về các lớp như sau:

  • Lớp thứ nhất về cú pháp lập trình, trước đây chắc hẳn chúng ta đã khá thân quen với các cú pháp dựa trên các annotation trong Spring MVC. Cú pháp này giờ đây không còn là độc quyền với Spring MVC nữa mà ngay cả Webflux cũng có thể dùng nó. Điều này giúp cho việc chuyển ứng dụng từ Spring MVC sang dùng Webflux trở nên tốn ít công sức hơn vì không phải thay đổi lại quá nhiều code. Ngoài ra, người sử dụng Webflux còn có thể viết chương trình theo cú pháp hoàn toàn mới đó là Router Function, cú pháp mới này mang đến phong cách lập trình hàm trong việc định nghĩa và khai báo các URL cho ứng dụng web. Router Functions sẽ chỉ áp dụng cho Webflux mà thôi.
  • Lớp thứ hai về bản thân framework ta sử dụng, trong trường hợp này là Spring MVC và Spring Webflux
  • Lớp thứ ba là chuẩn giao tiếp với các web server mà ứng dụng chạy trên đó. Spring MVC tuân theo chuẩn Servlet, còn Spring Webflux thì tuân theo chuẩn mới là Reactive Streams cho giao thức HTTP. Với chuẩn Reactive Streams thì các ứng dụng Webflux sẽ được chạy trên các webserver hỗ trợ các xử lý Non-Blocking để tối ưu tài nguyên tính toán.
  • Lớp thứ tư là các web server hỗ trợ các chuẩn giao tiếp ở bên trên. Đối với Spring MVC thì ứng dụng sẽ cần phải được chạy trên các Servlet Container ví dụ như Tomcat, Jetty … nhưng sang đến Spring Webflux thì lại không hạn chế như vậy. Đối với các servlet container tuân theo chuẩn Servlet 3.1 có hỗ trợ Non-Blocking IO, thì chúng cũng có thể chạy cùng với các ứng dụng Webflux. Ngoài ra, đối với các ứng dụng Webflux thì các Servlet container sẽ không phải là sự lựa chọn duy nhất, mà các HTTP Server như Netty hay Undertow có thể là những lựa chọn tuyệt vời khác cho việc xử lý không đồng bộ các HTTP Request đến từ ứng dụng Webflux.

Ví dụ ở trong bài viết này sẽ là ví dụ trong bài viết “Một cách nhìn khác về ứng dụng web với Spring MVC, Spring Boot, Tomcat dạng nhúng và Thymeleaf“, trong bài viết trước đó thì ví dụ là một ứng dụng web sử dụng Spring MVC chạy trên nền một tomcat web server dưới dạng nhúng. Sang đến bài viết này ta sẽ chuyển ứng dụng sang sử dụng Spring Webflux và chạy trên nền của Netty webserver cũng dưới dạng nhúng.

Đầu tiên về cấu trúc dự án thì vẫn là một Maven project như đã trình bày ở bài viết trước. Tất nhiên trong bài này ta chỉ có một số các thay đổi nhỏ về mặt tên gọi. Ví dụ như tên package là springmvcdemo sẽ được chuyển thành springwebfluxdemo, Spring5MVCDemoApp.java sẽ đổi tên thành Spring5WebfluxDemoApp.java như hình dưới đây:

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>com.codersontrang</groupId>
    <artifactId>spring-webflux-with-boot</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <repositories>
        <repository>
            <id>spring-milestone</id>
            <name>spring milestone</name>
            <url>http://repo.spring.io/milestone/</url>
        </repository>
        <repository>
            <id>maven-central</id>
            <name>maven central</name>
            <url>http://central.maven.org/maven2/</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
            <version>2.0.0.M4</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
            <version>2.0.0.M4</version>
        </dependency>

        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
            <version>3.0.8.RELEASE</version>
        </dependency>

    </dependencies>
</project>


Nếu so sánh với bài viết trước, thì dependency ta dùng ở đây có tên là spring-boot-starter-webflux thay vì spring-boot-starter-web (là dependency hỗ trợ cho Spring MVC). Tiếp theo đó dependency là spring-boot-starter-thymeleaf được nâng cấp phiên bản lên 2.0.0.M4 thay vì 2.0.0.M3. Ngoài ra, ta tường minh khai báo thêm một dependency nữa là thymeleaf-spring5 phiên bản 3.0.8.RELEASE để có thể chạy thymeleaf như là một template engine cho ứng dụng web.

Một số các file khác như Spring5WebfluxDemoApp.java hay Student.java sẽ không có gì thay đổi so với bài viết trước (ngoài trừ thay đổi tên)

Spring5WebfluxDemoApp.java


package springwebfluxdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Spring5WebfluxDemoApp {
	public static void main(String[] args) {
		SpringApplication.run(Spring5WebfluxDemoApp.class, args);
	}
}

Student.java


package springwebfluxdemo.model;

import java.util.List;

public class Student {
    private String name;
    private List<String> books;

    public List<String> getBooks() {
        return books;
    }
    public void setBooks(List<String> books) {
        this.books = books;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

DemoController.java


package springwebfluxdemo.controller;

import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import springwebfluxdemo.model.Student;

import java.util.ArrayList;
import java.util.List;

@Controller
public class DemoController {
    @GetMapping(value="/home")
    public String handleRequest(
                                ServerHttpRequest request,
                                ServerHttpResponse response,
                                Model model) throws Exception {

        Student student = new Student();
        student.setName("Coder Tien Sinh");

        List<String> books = new ArrayList<String>();
        books.add("book1");
        books.add("book2");
        student.setBooks(books);

        model.addAttribute("model", student);
        return "index";
    }

    @PostMapping(value="/submitStudentInfo")
    public String submitStudentInfo(Model model, @ModelAttribute("model") Student student){

        List<String> listBooks = student.getBooks();
        boolean bookSameName = checkSameBookName(listBooks);

        String message = "Update success !!!";
        if(bookSameName){
            message = "Books must have different name !!!";
        }
        model.addAttribute("message", message);

        model.addAttribute("model", student);
        return "index";
    }

    private boolean checkSameBookName(List<String> listBooks) {
        for(int i =0; i<listBooks.size(); i++){
            String firstBookName = listBooks.get(i);
            for(int j = i+1; j<listBooks.size(); j++){
                String secondBookName = listBooks.get(j);
                if(firstBookName.equalsIgnoreCase(secondBookName)){
                    return true;
                }
            }
        }
        return false;
    }
}

Trong file DemoController.java, về cơ bản so với bài viết trước thì ta không có nhiều thay đổi bởi vì ta vẫn sử dụng cú pháp với các annotation. Tuy nhiên, do Spring Webflux không dựa trên chuẩn Servlet, cho nên một số thứ sẽ phải thay đổi ở đây như sau:

  • org.springframework.web.servlet.ModelAndView sẽ không dùng được ở đây nữa, vì thế ta phải dùng cách khác để định nghĩa lại cơ chế điều hướng ở đây. Cụ thể là các phương thức thay vì trả về đối tượng của ModelAndView thì ta sẽ trả về giá trị String mà bản thân nó chính là tên của file view nằm trong thư mục /templates và có nội dung tuân theo cú pháp của Thymeleaf
  • Hai đối tượng thuộc kiểu javax.servlet.http.HttpServletRequestjavax.servlet.http.HttpServletResponse trong phương thức handleRequest() sẽ lần lượt chuyển thành org.springframework.http.server.reactive.ServerHttpRequestorg.springframework.http.server.reactive.ServerHttpResponse, đây là hai kiểu mới được định nghĩa từ Spring 5 để hỗ trợ giao tiếp với các webserver có hỗ trợ Non-Blocking và phương thức lập trình Reactive.

index.html


<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><title>Example :: Spring Application</title></head>
<body>
    <h1>CoderSonTrang - Spring Webflux Demo</h1>
    <h3>Student Book Show</h3>
    <form th:action="@{/submitStudentInfo}" th:method="POST" th:object="${model}">
        Your name: <span th:text="*{name}"></span>
        <input type="hidden" th:field="*{name}"/>

        <br/><br/>

        Edit your list of books:
        <input th:each="book,iterStat : *{books}" th:field="*{books[__${iterStat.index}__]}" />

        <input type="submit" value="Submit"/>
    </form>

    <div style="background:#972F2C;color: #FFF;" th:text="${message}"></div>

</body>
</html>

File index.html với nội dung là cú pháp của Thymeleaf sẽ không có gì thay đổi so với bài viết trước ngoại trừ các đoạn văn bản có dính dáng đến Spring MVC sẽ đổi thành Spring Webflux.

Kích hoạt chạy chương trình từ file Spring5WebfluxDemoApp.java, ta sẽ thấy webserver là Netty ở dạng nhúng được bật lên thay vì Tomcat như ở bài viết trước. Và Web Server này cũng lắng nghe các yêu cầu HTTP ở cổng 8080 như hình dưới đây.

Truy cập vào địa chỉ http://localhost:8080/home và làm các thao tác nhập liệu ta vẫn có được kết quả giống như ứng dụng viết bằng Spring MVC đươc trình ở bày viết trước.

Khi nhập hai quyển sách cùng tên là “book 1” và bấm Submit ta nhận được lời nhắn là “Books must have different name !!!

Khi nhập “book 1” và “book 3” và bấm Submit, ta nhận được lời nhắn là “Update success !!!

Như vậy là bài viết này đã đưa các bạn qua một ví dụ về việc sử dụng Spring Webflux là một framework cho việc phát triển các ứng dụng Web có từ Spring 5. Các bạn có thể để ý thấy là việc chuyển đổi ứng dụng từ Spring MVC sang Spring Webflux không phải là quá mất nhiều công sức trong khi đó Spring Webflux về lý thuyết mang lại khá là nhiều lợi ích cùng với phong cách lập trình hiện đại so với người anh em già của nó là Spring MVC. Hi vọng bài viết đã cung cấp cho các bạn một bước tiếp cận đầu tiên đến framework mới này trong hệ sinh thái của Spring. Và tất nhiên rồi, trong tương lai Coder Sơn Trang sẽ cố gắng để ra nhiều các bài viết khác để nói kĩ hơn về các chủ đề nhỏ hơn trong Spring Webflux.

Good luck!

2 comments on “Giới thiệu về Spring Webflux

  1. Pingback: Xác thực cơ bản và phân quyền bằng Spring Security cho ứng dụng Webflux | Coder Sơn Trang

  2. Pingback: Sử dụng Router Function trong Spring Webflux | Coder Sơn Trang

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất /  Thay đổi )

Google photo

Bạn đang bình luận bằng tài khoản Google Đăng xuất /  Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất /  Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất /  Thay đổi )

Connecting to %s