Files
notes_estom/Spring/Spring5/06 Webflux.md
2022-10-14 23:43:36 +08:00

18 KiB
Raw Blame History

WebFlux

这一块了解就行,不用掌握。

1 WebFlux简介

第一轮:就是看视频教程学会所有技术的原理和基本使用方法 第二轮阅读官方的文档尤其是Spring、Java、Maven等掌握编程的细节。

简介

Spring5添加的新模块。用于web开发的功能与SpringMVC类似。Webflux使用与当前比较流行的响应式编程出现的框架。

传统的Web框架比如SpringMVC基于Servlet容器Webflux是一种异步非阻塞的框架。异步非阻塞的框架在Servlet3.1后才支持)

其和虚拟式基于Reactor的相关API实现的。

异步非阻塞

  • 异步和同步是针对调用者,调用者发送请求,如果等着对方回应之后采取做其他事情就是同步,如果发送后不等着对方回应就去做其他事情就是异步。

  • 阻塞和非阻塞针对被调用者,被调用者收到一个请求之后,做完嘞请求任务之后,次啊给出返回,就阻塞,收到请求之后马上给出反馈然后再去做事情就是非阻塞。

与SpringMVC对比

  • 异步非阻塞,在有限的资源下,能够处理更多的请求,提高系统地吞吐量。
  • 函数式编程。Java最基本的编程模式。能够使用Java函数式编程的特点。
  • 两个框架都可以使用注解方式运行都可以运行在Tomcat等Servlet容器中。但SpringMVC采用命令式编程WebFlux使用响应式编程。

使用场景:网关

  • 需要处理大量的请求。所有客户调用网关,网关负责调用其他的组件。可以使用异步的方式。

2 网络技术栈

参考文档

web服务器

部署动态网站一般需要 Web 服务器的支持,例如:

  • 运行 PHP 网站一般选择 Apache 或者 Nginx
  • 运行 ASP/ASP.NET 网站一般选择 IIS
  • 运行 Python 网站一般选择内置的 WSGI 服务器模块——wsgiref。

Web 服务器是一种对外提供 Web 服务的软件,它可以接收浏览器的 HTTP 请求,并将处理结果返回给浏览器。

在部署 Servlet 网站时,同样需要一种类似的软件,例如 Tomcat、Jboss、Jetty、WebLogic 等但是它们通常被称为“容器”而不是“服务器”这究竟是为什么呢Servlet 容器和传统意义上的服务器有什么不同呢?

我们通常所说的 Web 服务器,比如 Apache、Nginx、IIS 等,它们的功能往往都比较单一,只能提供 http(s) 服务让用户访问静态资源HTML 文档、图片、CSS 文件、JavaScript 文件等),它们不能执行任何编程语言,也不能访问数据库,更不能让用户注册和登录。

也就是说,如果只有 Web 服务器那您只能部署静态网站不能部署动态网站。要想部署动态网站必须要有编程语言运行环境运行时Runtime的和数据库管理系统的支持。

部署动态网站一般至少需要三个组件,分别是 Web 服务器、脚本语言运行时和数据库,例如,部署 PHP 网站一般选择「Apache + PHP 运行时 + MySQL」的组合。

Web容器

简单来说Web容器就是让java能够像脚本语言一样响应用户请求的工具。 Servlet 是基于 Java 语言的,运行 Servlet 必然少不了 JRE 的支持,它负责解析和执行字节码文件(.class文件。然而 JRE 只包含了 Java 虚拟机JVM、Java 核心类库和一些辅助性性文件,它并不支持 Servlet 规范。要想运行 Servlet 代码,还需要一种额外的部件,该部件必须支持 Servlet 规范,实现了 Servlet 接口和一些基础类,这种部件就是 Servlet 容器。

Servlet 容器就是 Servlet 代码的运行环境(运行时),它除了实现 Servlet 规范定义的各种接口和类,为 Servlet 的运行提供底层支持,还需要管理由用户编写的 Servlet 类,比如实例化类(创建对象)、调用方法、销毁类等。

Servlet 中的容器和生活中的容器是类似的概念生活中容器用来装水、装粮食Servlet 中的容器用来装类,装对象。

读者可能会提出疑问,我们自己编写的 Servlet 类为什么需要 Servlet 容器来管理呢?这是因为我们编写的 Servlet 类没有 main() 函数,不能独立运行,只能作为一个模块被载入到 Servlet 容器,然后由 Servlet 容器来实例化,并调用其中的方法。

一个动态页面对应一个 Servlet 类,开发一个动态页面就是编写一个 Servlet 类当用户请求到达时Servlet 容器会根据配置文件web.xml来决定调用哪个类。

  1. 您看Web 服务器是整个动态网站的“大门”,用户的 HTTP 请求首先到达 Web 服务器Web 服务器判断该请求是静态资源还是动态资源:如果是静态资源就直接返回,此时相当于用户下载了一个服务器上的文件;如果是动态资源将无法处理,必须将该请求转发给 Servlet 容器。
  2. Servlet 容器接收到请求以后会根据配置文件web.xml找到对应的 Servlet 类将它加载并实例化然后调用其中的方法来处理用户请求处理结束后Servlet 容器将处理结果再转交给 Web 服务器,由 Web 服务器将处理结果进行封装,以 HTTP 响应的形式发送给最终的用户。

常用的 Web 容器有 Tomcat、Jboss、Jetty、WebLogic 等,其中 Tomcat 由 Java 官方提供,是初学者最常使用的。

为了简化部署流程Web 容器往往也会自带 Web 服务器模块,提供基本的 HTTP 服务,所以您可以不用再安装 Apache、IIS、Nginx 等传统意义上的服务器,只需要安装一款 Web 容器,就能部署 Servlet 网站了。正是由于这个原因,有的教材将 Tomcat 称为 Web 容器,有的教材又将 Tomcat 称为 Web 服务器,两者的概念已经非常模糊了。

将 Web 容器当做服务器使用后,上面的流程图就变成了下面的样子:

注意Servlet 容器自带的 Web 服务器模块虽然没有传统的 Web 服务器强大,但是也足以应付大部分开发场景,对初学者来说是足够的。当然,您也可以将传统的 Web 服务器和 Servlet 容器组合起来,两者分工协作,各司其职,共同完成 HTTP 请求。

总结,Servlet 容器就是 Servlet 程序的运行环境,它主要包含以下几个功能:

  • 实现 Servlet 规范定义的各种接口和类,为 Servlet 的运行提供底层支持;
  • 管理用户编写的 Servlet 类,以及实例化以后的对象;
  • 提供 HTTP 服务,相当于一个简化的服务器。

容器提供什么

  • 通讯支持利用容器提供的方法可以轻松的让servlet与web服务器对话无需自己建立ServerSocket、监听端口、创建流等。容器知道自己与Web服务器之间的协议。
  • 生命周期的管理控制着Servlet的生与死。
  • 多线程支持容器会自动的为其接收的每个Servlet请求创建一个新的Java线程。
  • 声明方式实现安全利用容器可以使用XML部署描述文件来配置和修改安全性而不必将其硬编码写入到Servlet(或其他)类代码中。
  • JSP支持将JSP翻译成Java。

3 响应式编程

响应式编程定义

响应式编程是一种面向数据流和变化产波的编程范式。

意味着可以在编程语言很方便地表达静态或者动态的数据流,

一个响应式编程的典型例子。D1=B1+C1。当B1的值修改后D1的值也会修改。B1的数据变化流向了D1。

Java8响应式编程

是要使用观察者模式实现了响应式编程。使用响应式编程Observer,Observable实现。

/**
 * Alipay.com Inc.
 * Copyright (c) 2004-2022 All Rights Reserved.
 */
package com.ykl.shangguigu08.reactor;

import java.util.Observable;

/**
 * @author yinkanglong
 * @version : ObserverDemo, v 0.1 2022-10-12 19:47 yinkanglong Exp $
 */
public class ObserverDemo extends Observable {

    /**
     * 通过Java8中的类实现响应式编程。
     * 简单来说,就是观察值模式。
     * @param args
     */
    public static void main(String[] args) {
        ObserverDemo observerDemo = new ObserverDemo();

        observerDemo.addObserver((o,arg)->{
            System.out.println("发生变化");
        });

        observerDemo.addObserver((o,arg)->{
            System.out.println("准备改变");
        });

        observerDemo.setChanged();
        observerDemo.notifyObservers();
    }
}

java9响应式编程

主要通过Flow类的sub和sub订阅消息实现响应式编程。

感觉这个响应式编程和awt控件的点击相应式操作很相似。但是不是启动新的线程。

响应式编程Reator实现

  • 响应式编程操作Reactor是满足Reactive规范框架
  • Reactor有两个核心类Mono和Flux这两个类实现接口Publisher提供丰富操作符号Flux对象实现发布返回N个元素。Mono实现发布者返回0或者1个元素。
  • Flux和Mono都是数据流的发布者。能够发出三种信号
    • 元素值
    • 完成信号。一种终止信号。订阅者数据流已经结束了。
    • 错误信号。一种终止信号。终止数据流并把错误信息传递给订阅者。

三种信号的特点

  • 错误信号和完成信号都是终止信号不能共存。
  • 如果没有发送任何元素值,而是直接发送错误或者完成信号,表示空数据流
  • 如果没有错误信号,没有完成信号,表示无限数据流。

实例Flux&Mono

引入相关的依赖

        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-core</artifactId>
            <version>3.1.5.RELEASE</version>
        </dependency>

进行发布者发布内容

package com.ykl.shangguigu08.reactor;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

/**
 * @author yinkanglong
 * @version : TestReactor, v 0.1 2022-10-13 10:25 yinkanglong Exp $
 */
public class TestReactor {

    public static void main(String[] args) {
        //reactor中的核心语法
        Flux.just(1,2,3,4);
        Mono.just(1);

        //其他方法
        Integer[] array = {1,2,3,4};
        Flux.fromArray(array);

        List<Integer> list = Arrays.asList(array);
        Flux.fromIterable(list);
        
        Stream<Integer> stream = list.stream();
        Flux.fromStream(stream);
    }
}
  • just等发布方法只是声明了数据流。只有声明了订阅者才会触发数据流不订阅就不会触发。
Flux.just(1,2,3,4).subscribe(System.out::print);
Mono.just(1).subscribe(System.out::print);

操作符

对数据流进行一道道操作,成为操作符,比如工厂流水线。

  • 操作符map。将元素映射为新的元素。
  • 操作符flatmap。元素映射为流。

4 WebFlux执行流程和核心API

Netty的基本原理

SpringWebflux基于Reactor默认使用容器NettyNetty是高性能的NIO框架异步非阻塞框架。

  1. BIO阻塞

  1. NIO非阻塞

SpringWebFlux

  • SpringWebflux核心控制器DispatchHandler实现接口WebHandler

关键类

DispatcherHandler负责请求处理。有三个核心类。

  • HandlerMappingreactor反应器请求查询到处理方法。
  • HandlerAdapter真正负责请求处理processor部分
  • HandlerResultHandler对结果进行处理

函数式编程实现

两个核心接口。

  • RouterFunction 路由处理
  • HandlerFunction处理函数

5 WebFlux基于注解的编程的实现

创建WebFlux项目

  1. 创建Springboot项目引入webflux的依赖
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>shangguigu09</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>shangguigu09</name>
    <description>shangguigu09</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <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>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  1. 在配置文件中设置启动端口号8081
server.port =8081
  1. 从上到下设计代码:创建接口和实现类
@Service
public class UserServiceImpl implements UserService {
    private final Map<Integer,User> users = new HashMap<>();

    public UserServiceImpl() {

        this.users.put(1,new User("lucy","nan",10));
        this.users.put(2,new User("mary","nv",38));
        this.users.put(3,new User("jack","nv",32));

    }

    @Override
    public Mono<User> getUserById(int id) {
        return Mono.justOrEmpty(this.users.get(id));
    }

    @Override
    public Flux<User> getAllUser() {
        return Flux.fromIterable(this.users.values());
    }

    @Override
    public Mono<Void> savaUserInfo(Mono<User> userMono) {
        return userMono.doOnNext(person->{
            int id = users.size() + 1;
            users.put(id,person);
        }).thenEmpty(Mono.empty());
    }
}
  1. 从下到上实现代码:实现业务逻辑

@RestController
public class UserController {
    @Autowired
    private UserService userService;

    //id
    @GetMapping("/user/{id}")
    public Mono<User> getUserById(@PathVariable int id){
        return userService.getUserById(id);
    }

    //all
    @GetMapping("/user")
    public Flux<User> getAllUser(){
        return userService.getAllUser();
    }
    //tianjian
    @GetMapping("/saveuser")
    public Mono<Void> saveUser(@RequestBody User user){
        Mono<User> userMono = Mono.just(user);
        return userService.savaUserInfo(userMono);
    }
}

实现说明

  • SpringMVC范式同步阻塞方式基于SpringMVC+Servlet+Tomcat
  • SpringWebflux方式异步非阻塞方式基于SpringMVCWebflux+Reactor+Netty

6 WebFlux基于函数的编程的实现

简要说明

bio,nio,aio 在使用函数式编程,需要自己初始化服务器

基于函数式编程模型的时候,有两个核心接口。

  • RouterFunction 实现路由功能请求转发给对应的handler
  • HandlerFunction 处理请求生成响应函数。

核心任务定义两个函数式接口的实现,并启动需要的服务器。

SpringWebFlux的请求和响应是

  • ServerRequest
  • ServerResponse

实现流程

  1. 从上到下实现业务bean
  2. 创建handler实现Mono方法

public class UserHandler {

    private final UserService userService;
    public UserHandler(UserService userService){
        this.userService = userService;
    }

    //根据id
    public Mono<ServerResponse> getUserById(ServerRequest request){
        //获取id值
        int  userid = Integer.valueOf( request.pathVariable("id"));
        Mono<ServerResponse> notFound = ServerResponse.notFound().build();
        //调用service方法取得数据
        Mono<User> userMono = this.userService.getUserById(userid);

        //UserMono进行转换返回。Reactor操作符
        return userMono.flatMap(person->ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
                .body(fromObject(person)))
                .switchIfEmpty(notFound);

    }

    //所有用户
    public Mono<ServerResponse> getAllUsers(){
        Flux<User> users = this.userService.getAllUser();
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users,User.class);

    }


    //添加
    public Mono<ServerResponse> saveUser(ServerRequest request){
        Mono<User> userMono = request.bodyToMono(User.class);
        return ServerResponse.ok().build(this.userService.savaUserInfo(userMono));
    }
}
  1. 创建并初始化服务器设置路由和handler
public class Server {
    //创建路由
    public RouterFunction<ServerResponse> route(){
        UserService userService = new UserServiceImpl();
        UserHandler handler = new UserHandler(userService);

        return RouterFunctions.route(GET("/users/{id}").and(accept(MediaType.APPLICATION_JSON)),handler::getUserById);
//                .andRoute(GET("users").and(accept(MediaType.APPLICATION_JSON)),handler::getAllUsers)
//                .andRoute(GET("saveuser").and(accept(MediaType.APPLICATION_JSON)),handler::saveUser);

    }

    public void createReactorServer(){
        RouterFunction<ServerResponse> route = route();
        HttpHandler httpHandler = toHttpHandler(route);

        ReactorHttpHandlerAdapter reactorHttpHandlerAdapter = new ReactorHttpHandlerAdapter(httpHandler);

        HttpServer httpServer = HttpServer.create();
        httpServer.handle(reactorHttpHandlerAdapter).bindNow();
    }

    public static void main(String[] args) throws Exception{
        Server server = new Server();
        server.createReactorServer();
        System.out.println("enter to exit");
        System.in.read();
    }
}

WebClient调用

public class Client {

    public static void main(String[] args) {
        WebClient webClient = WebClient.create("http://127.0.0.1:62418");
        User userMono = webClient.get().uri("/users/{id}", "1").accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(User.class).block();
        System.out.println(userMono.getName());
    }
}