基于函数式编程模型实现 Spring WebFlux

1,650 阅读4分钟

「这是我参与2022首次更文挑战的第31天,活动详情查看:2022首次更文挑战

在使用函数式编程模型时,我们需要自行初始化服务器。

在基于函数式的编程模型中,有两个核心的接口,分别是 RouterFunction 和 HandlerFunction,

其中 RouterFunction 实现了路由功能,将请求转发给对应的 handler,

HandlerFunction 则代表了处理传入请求并生成响应的函数。

在这种编程模型中,我们的核心任务就是定义这两个函数式接口的实现并启动所需的服务器。

在Spring MVC中,代表服务端HTTP请求和响应的两个对象是 ServletRequest 和 ServletRespose,但在 Spring WebFlux 中,代表服务器端HTTP请求和响应的两个对象是 ServerRequest 和 ServerRespose。

1、基于函数式编程模型实现 Spring WebFlux

1、创建Springboot项目,添加相关的依赖

    <!--webflux依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
        <version>2.6.3</version>
    </dependency>

    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.16</version>
    </dependency>

2、编写配置文件

server.port=8081

3、编写文件目录结构

实体类:User

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private int age;
}

service层接口:UserService

public interface UserService {

    //根据id查询,返回一个或0个使用 Mono
    Mono<User> queryById(int id);

    //查询所有,返回多个使用 Flux
    Flux<User> query();

    //添加用户
    Mono<Void> insert(Mono<User> user);
}

4、创建 Handler(具体的实现方法)

注意导包的时候,要导入 reactive 下的包

package com.haoming.handler;

import com.haoming.entity.User;
import com.haoming.service.UserService;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.servlet.ServletException;
import java.io.IOException;

import static org.springframework.web.reactive.function.BodyInserters.fromObject;
import static org.springframework.web.reactive.function.BodyInserters.fromValue;

public class UserHandler {

    private final UserService userService;

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

    //根据id查询
    public Mono<ServerResponse> queryById(ServerRequest serverRequest){
        //从请求中获取id
        int id = Integer.valueOf(serverRequest.pathVariable("id")) ;

        //空值处理,notFound():创建一个404 Not Found状态的构建器,build():构建没有内容的响应实体
        Mono<ServerResponse> build = ServerResponse.notFound().build();

        Mono<User> userMono = this.userService.queryById(id);

        /** 使用操作符把userMono转换成 Mono<ServerResponse> 类型,
         * flatMap:flatMap返回一个新的Mono,
         * ok():创建一个将状态设置为200 OK的构建器
         * contentType:设置正文的类型
         * body:将响应的主体设置为给定的BodyInserter并返回
         * switchIfEmpty:如果flatMap返回的是空,就调用空值处理
         */
        return userMono.flatMap(person -> ServerResponse
                .ok()
                .contentType(MediaType.APPLICATION_JSON)
                .body(fromValue(person))
                .switchIfEmpty(build));
    }

    //查询所有
    public Mono<ServerResponse> query(){
        Flux<User> users = this.userService.query();
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users,User.class);
    }

    //添加
    public Mono<ServerResponse> insert(ServerRequest serverRequest) throws ServletException, IOException {
        //获得user对象
        Mono<User> userMono = serverRequest.bodyToMono(User.class);
        return ServerResponse.ok().build(this.userService.insert(userMono));
    }
}

5、初始化服务器,并编写Router(实现路由功能)

package com.haoming;


import com.haoming.handler.UserHandler;
import com.haoming.service.UserService;
import com.haoming.service.impl.UserServiceImpl;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.netty.http.server.HttpServer;

import java.io.IOException;

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;

public class Server {

    //最终调用
    public static void main(String[] args) throws IOException {
        Server server = new Server();
        server.createReactorServer();
        System.out.println("enter to exit");
        //从输入流中读取下一个字节的数据
        System.in.read();
    }
    //创建路由
    public RouterFunction<ServerResponse> routerFunction(){
        UserService userService = new UserServiceImpl();
        UserHandler userHandler = new UserHandler(userService);

        //通过 /users/{id} 就可以绑定到 Handler里面的具体的方法,并指定以JSON的格式返回
       return RouterFunctions.route(
                GET("/users/{id}").and(accept(APPLICATION_JSON)),userHandler::queryById)
               .andRoute(GET("/query").and(accept(APPLICATION_JSON)),userHandler::query);
    }
    //创建服务器完成适配
    public void createReactorServer(){
        //路由和handler处理器适配
        RouterFunction<ServerResponse> route = routerFunction();
        /**
         * route:要转换的路由器功能
         * 返回:使用给定的路由器函数routerFunction()处理http请求的http处理器
         */
        HttpHandler httpHandler = toHttpHandler(route);
        //适配器
        ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);

        //创建HTTP服务器,HttpServer:表示一个服务器实例
        HttpServer httpServer = HttpServer.create();
        //bindNow():以阻塞方式启动服务器,等待它完成初始化
        httpServer.handle(adapter).bindNow();
    }
}

启动服务器进行测试:

在这里插入图片描述

启动成功后可以看到服务器的端口号是55953,下面使用路由进行测试

测试根据查询:http://localhost:55953/users/1

在这里插入图片描述

测试查询全部:http://localhost:55953/query

在这里插入图片描述

测试成功!

2、使用WebClient 调用

WebClient 提供的基于响应式的非阻塞的web请求客户端,对应的是老式的restTemplate这类,但相对于老式的restTemplate,他不阻塞代码、异步执行。

编写WebClient :

package com.haoming;

import com.haoming.entity.User;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;

public class Client {
    public static void main(String[] args) {
        //调用服务器的地址
        WebClient webClient = WebClient.create("http://127.0.0.1:55953");

        //根据id查询
        String id = "1";
        /**
         * get().uri("/users/{id}", id):指定请求的URI
         * retrieve():执行HTTP请求并接收JSON格式的正文
         * bodyToMono(User.class):指定请求结果需要处理为 User,并包装为Reactor的Mono对象
         * block():阻塞获取响应的结果
         */
        User user = webClient.get().uri("/users/{id}", id).accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(User.class).block();
        //打印用户姓名
        System.out.println(user.getName());

        //查询所有
        Flux<User> user1 = webClient.get().uri("/query", id).accept(MediaType.APPLICATION_JSON).retrieve().bodyToFlux(User.class);

        //打印用户信息
        user1.map(user2 -> user2).buffer().doOnNext(System.out::println).blockFirst();

    }
}

启动WebClient 测试:成功打印出查询的结果

在这里插入图片描述