GVKun编程网logo

WebFlux04 SpringBootWebFlux 集成 MongoDB 之 Windows 版本、WebFlux 实现 CRUD、WebFlux 实现 JPA、参数校验

19

在这里,我们将给大家分享关于WebFlux04SpringBootWebFlux集成MongoDB之Windows版本、WebFlux实现CRUD、WebFlux实现JPA、参数校验的知识,同时也会涉

在这里,我们将给大家分享关于WebFlux04 SpringBootWebFlux 集成 MongoDB 之 Windows 版本、WebFlux 实现 CRUD、WebFlux 实现 JPA、参数校验的知识,同时也会涉及到如何更有效地 Spring Boot 2.0 的 WebFlux、java-带@EnableWebFlux批注的SpringWebFlux错误、Spring Boot 2 快速教程:WebFlux 集成 Mongodb(四)、Spring Boot 2.0 WebFlux Web CRUD 实践的内容。

本文目录一览:

WebFlux04 SpringBootWebFlux 集成 MongoDB 之 Windows 版本、WebFlux 实现 CRUD、WebFlux 实现 JPA、参数校验

WebFlux04 SpringBootWebFlux 集成 MongoDB 之 Windows 版本、WebFlux 实现 CRUD、WebFlux 实现 JPA、参数校验

1 下载并安装 MongoDB

  1.1 MongoDB 官网

    

  1.2 下载

    solutions -> download center

    

  1.3 安装

    双击进入安装即可

    1.3.1 安装时常见 bug01

      

    1.3.2 bug01 解决办法

      

  1.4 启动 mongodb

    技巧 01:需要在同安装目录同一级别创建一个 data 目录来存放数据

    技巧 02:将下下面的命令存储成一个 bat 文件,下次启动时双击即可

C:\tool\mongoDB\bin\mongod --dbpath C:\tool\data --smallfiles

  1.5 mongodb 正常启动后的控制台信息

    

  1.6 启动 MongoDB 客户端

    双击 mongoDB 安装目录下 -> bin -> mongo.exe

    

    1.6.1 常用命令

      show databases -> 显示数据库

      use 数据库名称 -> 更换当前数据库

      show tables -> 查看当前数据库中的数据表

      db. 表名.find () -> 查看某个表中的所有数据

      db. 表名.find ().pretty () -> 查看某个表中的所有数据并进行格式化输出

      

 

2 SpringBootWebFlux 集成 MongoDB

  2.1 创建一个项目

    引入相关依赖:webflux、mongodb、devtool、lombok

<?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>cn.xiangxu</groupId>
    <artifactId>webflux_demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>webflux_demo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <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-devtools</artifactId>
            <!--<scope>runtime</scope>-->
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-mongodb-reactive -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb-reactive</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>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
        </dependency>
    </dependencies>

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


</project>
pom.xml

  2.2 启动类

    在启动类上添加 @EnableReactiveMongoRepositories 注解来开启 mongodb 相关的配置

package cn.xiangxu.webflux_demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;

@SpringBootApplication
@EnableReactiveMongoRepositories
public class WebfluxDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(WebfluxDemoApplication.class, args);
    }
}
View Code

  2.3 实体类

    @Document(collection = "user") 目的时定义在 mongodb 中的表名,相当于 JPA 中的 @Table 注解

    技巧 01:在 mongodb 中的主键一般都是 String 类型的

package cn.xiangxu.webflux_demo.domain;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

/**
 * @author 王杨帅
 * @create 2018-06-27 8:42
 * @desc
 **/
@Document(collection = "user")
@Data
public class User {
    @Id
    private String id;

    private String name;

    private int age;
}
User.java

  2.4 持久层

    只需要继承 ReactiveMongoRepository 接口就行啦,和 JPA 差不多

package cn.xiangxu.webflux_demo.repository;

import cn.xiangxu.webflux_demo.domain.User;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.stereotype.Repository;

/**
 * @author 王杨帅
 * @create 2018-06-27 8:44
 * @desc
 **/
@Repository
public interface UserRepository extends ReactiveMongoRepository<User, String> {
}
UserRepository.java

  2.5 控制层

    技巧 01:有两种返回方式,一种是把所有数据一次性返回,另一种是像流一样的进行返回

package cn.xiangxu.webflux_demo.web;

import cn.xiangxu.webflux_demo.domain.User;
import cn.xiangxu.webflux_demo.repository.UserRepository;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

/**
 * @author 王杨帅
 * @create 2018-06-27 8:45
 * @desc
 **/
@RestController
@RequestMapping(value = "/user")
public class UserController {

    private final UserRepository userRepository;

    /**
     * 利用构造器注入持久层对象
     * @param userRepository
     */
    public UserController(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    /**
     * 一次性返回
     * @return
     */
    @GetMapping(value = "/")
    public Flux<User> getAll() {
        return userRepository.findAll();
    }

    /**
     * 流式返回
     * @return
     */
    @GetMapping(value = "/stream/all", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<User> streamGetAll() {
        return userRepository.findAll();
    }


}
UserController.java

  2.6 配置文件

    前提:安装好 mongodb 并启动

spring.data.mongodb.uri=mongodb://localhost:27017/webflux

  2.7 启动并测试

    技巧 01:利用 postman 进行测试

    

 

3 WebFlux 实现 CRUD

  准备:SpringBootWebFlux 项目搭建以及 SpringBootWebFlux 集成 MongoDB 请参见上面的

  3.1 新增

    ReactiveCrudRepository 接口中的 save 方法可以实现更新和新增操作

    技巧 01:利用 save 方法进行更新操作时,如果接收到的 ID 在 mongodb 中没有对应的记录就会执行新增操作,而且新增数据的 ID 就是传过来的 ID 信息

    技巧 02:利用 save 方法进行新增操作时,不需要前端传 ID 信息,mongodb 会自动根据实体类生成 ID 信息;如果传了 ID 信息就会变成更新操作了

<S extends T> Mono<S> save(S var1);
/**
     * 新增用户
     * @param user
     * @return
     */
    @PostMapping
    public Mono<User> createUser(@RequestBody User user
    ) {
        return userRepository.save(user);

        /**
         * Note
         *  1 save 可以修改和新增,如果有ID就是修改【该ID存在,如果该ID不存在直接新增】,没有就是新增
         */
    }
View Code

    

  3.2 删除

    需求:根据前端传过来的 ID 信息删除用户信息,如果删除成功就返回 200 状态码,删除失败就返回 404 状态码

    坑 01:ReactiveCrudRepository 接口提供的一系列 delete 方法都没有返回自,所以不确定是否已经删除成功

Mono<Void> deleteById(ID var1);

    Mono<Void> deleteById(Publisher<ID> var1);

    Mono<Void> delete(T var1);

    Mono<Void> deleteAll(Iterable<? extends T> var1);

    Mono<Void> deleteAll(Publisher<? extends T> var1);

    Mono<Void> deleteAll();
View Code

    解坑 01:根据前端 I 传过来的 ID 查询信息 -> 查到就进行删除操作 -> 返回 200 状态码

                       -> 查不到就直接返回 404 状态码

    技巧 01:map 和 flatMap 的使用时机

      当要操作数据并返回 Mono 时使用 flatMap; 如果不操作数据,仅仅转换数据时使用 Map

    技巧 02:如果一个 stream 没有返回值但方法有要求有返回值时,可以利用 then 来返回数据

/**
     * 删除用户
     *  需求:删除成功后返回200状态码,删除失败就返回404状态码
     * @param id
     * @return
     */
    @DeleteMapping(value = "/{id}")
    public Mono<ResponseEntity<Void>> deleteUser(@PathVariable("id") String id) {
//        userRepository.deleteById(id); // deleteById 方法没有返回值,我们无法知道是否删除成功
        return userRepository.findById(id)
                .flatMap( // 根据ID找到数据进行删除操作,并返回200状态码
                        user -> userRepository.delete(user)
                                .then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)))
                )
                .defaultIfEmpty(new ResponseEntity<Void>(HttpStatus.NOT_FOUND)); // 如果没有查找到数据就返回404状态码

        /**
         * Note
         *  1 map和flatMap的使用时机
         *      当要操作数据并返回Mono时使用flatMap
         *      如果不操作数据,仅仅转换数据时使用Map
         *  2 如果一个stream没有返回值但方法有要求有返回值时,可以利用then来返回数据
         */

    }
View Code

  3.3 更新

    需求:根据前端传过来的 ID 和更新数据类进行更新操作;更新成功后返回 200 状态码和更新后的数据,更新失败后就返回 404 状态码

    坑 01:直接利用 save 方法进行更新操作时容易产生歧义,因为 save 方法可以进行更新和删除操作;当接收到对象没有 id 信息时就进行新增操作,如果有 ID 信息而且数据库有该 ID 对应的数据时就进行更新操作,如果有 ID 信息但是数据库中没有改 ID 对应的数据时也会进行新增操作

    解坑 01: 根据前端传过来的 ID 查询数据 -> 查到数据就进行更新操作 -> 返回 200 状态码和更新过后的数据

                       -> 没查到数据就返回 404 状态码

/**
     * 修改数据
     *      修改成功返回200和修改成功后的数据,不存在时返回404
     * @param id 要修改的用户ID
     * @param user 修改数据
     * @return
     */
    @PutMapping(value = "/{id}")
    public Mono<ResponseEntity<User>> updateUser(
            @PathVariable("id") String id,
            @RequestBody User user
    ) {
        return userRepository.findById(id)
                .flatMap( // 操作数据
                        u -> {
                            u.setAge(user.getAge());
                            u.setName(user.getName());
                            return userRepository.save(u);
                        }
                )
                .map(u -> new ResponseEntity<User>(u, HttpStatus.OK)) // 转化数据
                .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
    }
View Code

  3.4 查询

    需求:根据前端传过来的 ID 查询数据,如果查到就直接返回 200 状态码和查到的数据,如果不存在该 ID 对应的数据就直接返回 404 状态码

/**
     * 根据ID查找用户
     *      存在时返回200和查到的数据,不存在时就返回404
     * @param id 用户ID
     * @return
     */
    @GetMapping(value = "/{id}")
    public Mono<ResponseEntity<User>> getById(@PathVariable("id") String id) {

        return userRepository.findById(id)
                .map(u -> new ResponseEntity<User>(u, HttpStatus.OK)) // 转化数据
                .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));

    }
View Code

  ・3.5 代码汇总

package cn.xiangxu.webflux_demo.web;

import cn.xiangxu.webflux_demo.domain.User;
import cn.xiangxu.webflux_demo.repository.UserRepository;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * @author 王杨帅
 * @create 2018-06-27 8:45
 * @desc
 **/
@RestController
@RequestMapping(value = "/user")
public class UserController {

    private final UserRepository userRepository;

    /**
     * 利用构造器注入持久层对象
     * @param userRepository
     */
    public UserController(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    /**
     * 以数组形式一次性返回
     * @return
     */
    @GetMapping(value = "/")
    public Flux<User> getAll() {
        return userRepository.findAll();
    }

    /**
     * 以SSE形式流式返回
     * @return
     */
    @GetMapping(value = "/stream/all", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<User> streamGetAll() {
        return userRepository.findAll();
    }

    /**
     * 新增用户
     * @param user
     * @return
     */
    @PostMapping
    public Mono<User> createUser(@RequestBody User user
    ) {
        return userRepository.save(user);

        /**
         * Note
         *  1 save 可以修改和新增,如果有ID就是修改【该ID存在,如果该ID不存在直接新增】,没有就是新增
         */
    }

    /**
     * 删除用户
     *  需求:删除成功后返回200状态码,删除失败就返回404状态码
     * @param id
     * @return
     */
    @DeleteMapping(value = "/{id}")
    public Mono<ResponseEntity<Void>> deleteUser(@PathVariable("id") String id) {
//        userRepository.deleteById(id); // deleteById 方法没有返回值,我们无法知道是否删除成功
        return userRepository.findById(id)
                .flatMap( // 根据ID找到数据进行删除操作,并返回200状态码
                        user -> userRepository.delete(user)
                                .then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)))
                )
                .defaultIfEmpty(new ResponseEntity<Void>(HttpStatus.NOT_FOUND)); // 如果没有查找到数据就返回404状态码

        /**
         * Note
         *  1 map和flatMap的使用时机
         *      当要操作数据并返回Mono时使用flatMap
         *      如果不操作数据,仅仅转换数据时使用Map
         *  2 如果一个stream没有返回值但方法有要求有返回值时,可以利用then来返回数据
         */

    }

    /**
     * 修改数据
     *      修改成功返回200和修改成功后的数据,不存在时返回404
     * @param id 要修改的用户ID
     * @param user 修改数据
     * @return
     */
    @PutMapping(value = "/{id}")
    public Mono<ResponseEntity<User>> updateUser(
            @PathVariable("id") String id,
            @RequestBody User user
    ) {
        return userRepository.findById(id)
                .flatMap( // 操作数据
                        u -> {
                            u.setAge(user.getAge());
                            u.setName(user.getName());
                            return userRepository.save(u);
                        }
                )
                .map(u -> new ResponseEntity<User>(u, HttpStatus.OK)) // 转化数据
                .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
    }

    /**
     * 根据ID查找用户
     *      存在时返回200和查到的数据,不存在时就返回404
     * @param id 用户ID
     * @return
     */
    @GetMapping(value = "/{id}")
    public Mono<ResponseEntity<User>> getById(@PathVariable("id") String id) {

        return userRepository.findById(id)
                .map(u -> new ResponseEntity<User>(u, HttpStatus.OK)) // 转化数据
                .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));

    }

    /**
     * 根据年龄段查询:数组形式返回
     * @param start 最小年龄
     * @param end 最大年龄
     * @return
     */
    @GetMapping(value = "/age/{start}/{end}")
    public Flux<User> findByAge(
            @PathVariable("start") Integer start,
            @PathVariable("end") Integer end
    ) {
        return userRepository.findByAgeBetween(start, end);
    }

    /**
     * 根据年龄段查询:流式返回
     * @param start
     * @param end
     * @return
     */
    @GetMapping(value = "/stream/age/{start}/{end}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<User> streamFindByAge(
            @PathVariable("start") Integer start,
            @PathVariable("end") Integer end
    ) {
        return userRepository.findByAgeBetween(start, end);
    }


    /**
     * 查询年龄在20-30的用户
     * @return
     */
    @GetMapping(value = "/age/oldUser")
    public Flux<User> oldUser() {
        return userRepository.oldUser();
    }


    /**
     * 查询年龄在20-30的用户
     * @return
     */
    @GetMapping(value = "/stream/age/oldUser", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<User> streamFindByAge() {
        return userRepository.oldUser();
    }



}
View Code

  

4 WebFlux 实现 JPA

  ReactiveCrudRepository 接口除了提供简单的 CRUD 操作外,还可以进行自定义数据操作方法,但是自定义方法的方法名有一定的要求;如果定义的方法名不满足 SpringData JPA 也可以利用 @Query 注解使用原生的注解进行实现

  4.1 符合 JPA 规范的写法

    4.1.1 持久层

/**
     * 根据年龄段查询用户【PS: 不包括端点值】
     * @param start
     * @param end
     * @return
     */
    Flux<User> findByAgeBetween(Integer start, Integer end);
package cn.xiangxu.webflux_demo.repository;

import cn.xiangxu.webflux_demo.domain.User;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;

/**
 * @author 王杨帅
 * @create 2018-06-27 8:44
 * @desc
 **/
@Repository
public interface UserRepository extends ReactiveMongoRepository<User, String> {

    /**
     * 根据年龄段查询用户【PS: 不包括端点值】
     * @param start
     * @param end
     * @return
     */
    Flux<User> findByAgeBetween(Integer start, Integer end);

    /**
     * 查询年龄在20-30的用户【PS: 包括端点值】
     *      利用MongoDB的 SQL 语句实现
     * @return
     */
    @Query("{''age'':{''$gte'':20, ''$lte'':30}}")
    Flux<User> oldUser();

}
View Code

    4.1.2 控制层

/**
     * 根据年龄段查询:数组形式返回
     * @param start 最小年龄
     * @param end 最大年龄
     * @return
     */
    @GetMapping(value = "/age/{start}/{end}")
    public Flux<User> findByAge(
            @PathVariable("start") Integer start,
            @PathVariable("end") Integer end
    ) {
        return userRepository.findByAgeBetween(start, end);
    }

    /**
     * 根据年龄段查询:流式返回
     * @param start
     * @param end
     * @return
     */
    @GetMapping(value = "/stream/age/{start}/{end}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<User> streamFindByAge(
            @PathVariable("start") Integer start,
            @PathVariable("end") Integer end
    ) {
        return userRepository.findByAgeBetween(start, end);
    }
View Code

  4.2 @Query 的写法

    4.2.1 持久层

/**
     * 查询年龄在20-30的用户【PS: 包括端点值】
     *      利用MongoDB的 SQL 语句实现
     * @return
     */
    @Query("{''age'':{''$gte'':20, ''$lte'':30}}")
    Flux<User> oldUser();
package cn.xiangxu.webflux_demo.repository;

import cn.xiangxu.webflux_demo.domain.User;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;

/**
 * @author 王杨帅
 * @create 2018-06-27 8:44
 * @desc
 **/
@Repository
public interface UserRepository extends ReactiveMongoRepository<User, String> {

    /**
     * 根据年龄段查询用户【PS: 不包括端点值】
     * @param start
     * @param end
     * @return
     */
    Flux<User> findByAgeBetween(Integer start, Integer end);

    /**
     * 查询年龄在20-30的用户【PS: 包括端点值】
     *      利用MongoDB的 SQL 语句实现
     * @return
     */
    @Query("{''age'':{''$gte'':20, ''$lte'':30}}")
    Flux<User> oldUser();

}
View Code

    4.2.2 控制层

/**
     * 查询年龄在20-30的用户
     * @return
     */
    @GetMapping(value = "/age/oldUser")
    public Flux<User> oldUser() {
        return userRepository.oldUser();
    }


    /**
     * 查询年龄在20-30的用户
     * @return
     */
    @GetMapping(value = "/stream/age/oldUser", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<User> streamFindByAge() {
        return userRepository.oldUser();
    }
View Code

  4.3 代码汇总

    4.3.1 持久层

package cn.xiangxu.webflux_demo.repository;

import cn.xiangxu.webflux_demo.domain.User;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;

/**
 * @author 王杨帅
 * @create 2018-06-27 8:44
 * @desc
 **/
@Repository
public interface UserRepository extends ReactiveMongoRepository<User, String> {

    /**
     * 根据年龄段查询用户【PS: 不包括端点值】
     * @param start
     * @param end
     * @return
     */
    Flux<User> findByAgeBetween(Integer start, Integer end);

    /**
     * 查询年龄在20-30的用户【PS: 包括端点值】
     *      利用MongoDB的 SQL 语句实现
     * @return
     */
    @Query("{''age'':{''$gte'':20, ''$lte'':30}}")
    Flux<User> oldUser();

}
View Code

    4.3.2 控制层

package cn.xiangxu.webflux_demo.web;

import cn.xiangxu.webflux_demo.domain.User;
import cn.xiangxu.webflux_demo.repository.UserRepository;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * @author 王杨帅
 * @create 2018-06-27 8:45
 * @desc
 **/
@RestController
@RequestMapping(value = "/user")
public class UserController {

    private final UserRepository userRepository;

    /**
     * 利用构造器注入持久层对象
     * @param userRepository
     */
    public UserController(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    /**
     * 以数组形式一次性返回
     * @return
     */
    @GetMapping(value = "/")
    public Flux<User> getAll() {
        return userRepository.findAll();
    }

    /**
     * 以SSE形式流式返回
     * @return
     */
    @GetMapping(value = "/stream/all", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<User> streamGetAll() {
        return userRepository.findAll();
    }

    /**
     * 新增用户
     * @param user
     * @return
     */
    @PostMapping
    public Mono<User> createUser(@RequestBody User user
    ) {
        return userRepository.save(user);

        /**
         * Note
         *  1 save 可以修改和新增,如果有ID就是修改【该ID存在,如果该ID不存在直接新增】,没有就是新增
         */
    }

    /**
     * 删除用户
     *  需求:删除成功后返回200状态码,删除失败就返回404状态码
     * @param id
     * @return
     */
    @DeleteMapping(value = "/{id}")
    public Mono<ResponseEntity<Void>> deleteUser(@PathVariable("id") String id) {
//        userRepository.deleteById(id); // deleteById 方法没有返回值,我们无法知道是否删除成功
        return userRepository.findById(id)
                .flatMap( // 根据ID找到数据进行删除操作,并返回200状态码
                        user -> userRepository.delete(user)
                                .then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)))
                )
                .defaultIfEmpty(new ResponseEntity<Void>(HttpStatus.NOT_FOUND)); // 如果没有查找到数据就返回404状态码

        /**
         * Note
         *  1 map和flatMap的使用时机
         *      当要操作数据并返回Mono时使用flatMap
         *      如果不操作数据,仅仅转换数据时使用Map
         *  2 如果一个stream没有返回值但方法有要求有返回值时,可以利用then来返回数据
         */

    }

    /**
     * 修改数据
     *      修改成功返回200和修改成功后的数据,不存在时返回404
     * @param id 要修改的用户ID
     * @param user 修改数据
     * @return
     */
    @PutMapping(value = "/{id}")
    public Mono<ResponseEntity<User>> updateUser(
            @PathVariable("id") String id,
            @RequestBody User user
    ) {
        return userRepository.findById(id)
                .flatMap( // 操作数据
                        u -> {
                            u.setAge(user.getAge());
                            u.setName(user.getName());
                            return userRepository.save(u);
                        }
                )
                .map(u -> new ResponseEntity<User>(u, HttpStatus.OK)) // 转化数据
                .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
    }

    /**
     * 根据ID查找用户
     *      存在时返回200和查到的数据,不存在时就返回404
     * @param id 用户ID
     * @return
     */
    @GetMapping(value = "/{id}")
    public Mono<ResponseEntity<User>> getById(@PathVariable("id") String id) {

        return userRepository.findById(id)
                .map(u -> new ResponseEntity<User>(u, HttpStatus.OK)) // 转化数据
                .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));

    }

    /**
     * 根据年龄段查询:数组形式返回
     * @param start 最小年龄
     * @param end 最大年龄
     * @return
     */
    @GetMapping(value = "/age/{start}/{end}")
    public Flux<User> findByAge(
            @PathVariable("start") Integer start,
            @PathVariable("end") Integer end
    ) {
        return userRepository.findByAgeBetween(start, end);
    }

    /**
     * 根据年龄段查询:流式返回
     * @param start
     * @param end
     * @return
     */
    @GetMapping(value = "/stream/age/{start}/{end}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<User> streamFindByAge(
            @PathVariable("start") Integer start,
            @PathVariable("end") Integer end
    ) {
        return userRepository.findByAgeBetween(start, end);
    }


    /**
     * 查询年龄在20-30的用户
     * @return
     */
    @GetMapping(value = "/age/oldUser")
    public Flux<User> oldUser() {
        return userRepository.oldUser();
    }


    /**
     * 查询年龄在20-30的用户
     * @return
     */
    @GetMapping(value = "/stream/age/oldUser", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<User> streamFindByAge() {
        return userRepository.oldUser();
    }



}
View Code

 

5 参数校验

  技巧 01:参数校验和 MVC 模式相同,只需要在实体类的成员属性上添加相应的注解即可;然后在请求控制方法的参数上加上 @Valid 即可

  5.1 实体类

package cn.xiangxu.webflux_test.domain.domain_do;

import lombok.Data;
import org.hibernate.validator.constraints.Range;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import javax.validation.constraints.NotBlank;

/**
 * @author 王杨帅
 * @create 2018-08-02 15:30
 * @desc 学生实体类
 **/
@Document(collection = "student")
@Data
public class StudentDO {
    @Id
    private String id;

    @NotBlank
    private String name;
    private String address;
    @Range(min = 12, max = 50)
    private Integer age;
}
StudentDO.java

  5.2 持久层

package cn.xiangxu.webflux_test.reposigory;

import cn.xiangxu.webflux_test.domain.domain_do.StudentDO;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.stereotype.Repository;

/**
 * @author 王杨帅
 * @create 2018-08-02 15:31
 * @desc 学生持久层
 **/
@Repository
public interface StudentRepository extends ReactiveMongoRepository<StudentDO, String> {
}
StudentRepository.java

  5.3 控制层

    坑 01:在 MVC 模式时可以在控制方法上使用  BindingResult ,但是在 WebFlux 模式下不可以使用;只能通过创建切面进行异常捕获

    技巧 02:添加了 @Valid 注解后如果有参数不合法时抛出的异常是 WebExchangeBindException

package cn.xiangxu.webflux_test.controller;

import cn.xiangxu.webflux_test.domain.domain_do.StudentDO;
import cn.xiangxu.webflux_test.reposigory.StudentRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.validation.Valid;

import static cn.xiangxu.webflux_test.util.CheckUtil.checkeName;

/**
 * @author 王杨帅
 * @create 2018-08-02 15:44
 * @desc 学生控制层
 **/
@RestController
@RequestMapping(value = "/stu")
@Slf4j
public class StudentController {

    @Autowired
    private StudentRepository studentRepository;

    @GetMapping
    public Flux<StudentDO> findList() {
        return studentRepository.findAll();
    }

    @PostMapping
    public Mono<StudentDO> create(
            @Valid @RequestBody StudentDO studentDO) {
        System.out.println(studentDO);
        checkeName(studentDO.getName());
        return studentRepository.save(studentDO);
    }

    @PutMapping()
    public Mono<ResponseEntity<String>> update(
            @Valid @RequestBody StudentDO studentDO
    ) {
        log.info("前端传过来的数据为:" + studentDO);
        checkeName(studentDO.getName());
        return studentRepository.findById(studentDO.getId())
                .flatMap(student -> studentRepository.save(studentDO))
                .map(student -> new ResponseEntity<String>("更新成功", HttpStatus.OK))
                .defaultIfEmpty(new ResponseEntity<String>("ID不合法", HttpStatus.BAD_REQUEST));
    }

    @DeleteMapping(value = "/{id}")
    public Mono<ResponseEntity<Void>> delete(
            @PathVariable(value = "id") String id
    ) {
        log.info("从前端获取到的ID信息为:" + id);
        return studentRepository.findById(id)
                .flatMap(
                        student -> {
                            return studentRepository.deleteById(student.getId())
                                    .then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)));
                        }
                )
                .defaultIfEmpty(new ResponseEntity<Void>(HttpStatus.NOT_FOUND));
    }

    @GetMapping(value = "/{id}")
    public Mono<ResponseEntity<StudentDO>> findById(
            @PathVariable("id") String id
    ) {
        log.info("从前端获取到的参数信息为:" + id);
        return studentRepository.findById(id)
                .map(student -> new ResponseEntity<StudentDO>(student, HttpStatus.OK))
                .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
    }

}
StudentController.java

  5.4 编写异常处理切面

package cn.xiangxu.webflux_test.exception.handler;

import cn.xiangxu.webflux_test.exception.CheckException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.support.WebExchangeBindException;

import java.util.Optional;

/**
 * @author 王杨帅
 * @create 2018-08-02 16:31
 * @desc
 **/
@ControllerAdvice
@Slf4j
public class CheckAdvice {
    @ExceptionHandler(WebExchangeBindException.class)
    public ResponseEntity handleWebExchangeBindException(WebExchangeBindException e) {
        log.error(e.getMessage());

        return new ResponseEntity<String>(toStr(e), HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(CheckException.class)
    public ResponseEntity handleCheckException(CheckException e) {
        log.error(e.getMessage());
        return new ResponseEntity<String>(toStr(e), HttpStatus.BAD_REQUEST);
    }

    private String toStr(CheckException e) {
        return e.getFiledName() + " : " + e.getFiledValue();
    }

    /**
     * 把校验异常转化成字符串
     * @param e
     * @return
     */
    private String toStr(WebExchangeBindException e) {
        return e.getFieldErrors().stream()
                .map(error -> error.getField() + " : " + error.getDefaultMessage())
                .reduce("", (s1, s2) -> s1 + " \n " + s2);
    }
}
CheckAdvice.java

  5.5 自定义校验方法

    自定义校验方法有两种方式,一种自定义一个参数校验注解,另外一种是自定义一个参数校验方法【PS: 本博文基于后者】

     5.5.1 自定义异常

package cn.xiangxu.webflux_test.exception;

import lombok.Data;

/**
 * @author 王杨帅
 * @create 2018-08-02 17:01
 * @desc 检查异常
 **/
@Data
public class CheckException extends RuntimeException  {

    /**
     * 出错字段
     */
    private String filedName;

    /**
     * 出错值
     */
    private String filedValue;

    public CheckException(String message, String filedName, String filedValue) {
        super(message);
        this.filedName = filedName;
        this.filedValue = filedValue;
    }

    public CheckException(String filedName, String filedValue) {
        this.filedName = filedName;
        this.filedValue = filedValue;
    }
}
CheckException.java

    5.5.2 编写捕获自定义异常的切面

package cn.xiangxu.webflux_test.exception.handler;

import cn.xiangxu.webflux_test.exception.CheckException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.support.WebExchangeBindException;

import java.util.Optional;

/**
 * @author 王杨帅
 * @create 2018-08-02 16:31
 * @desc
 **/
@ControllerAdvice
@Slf4j
public class CheckAdvice {
    @ExceptionHandler(WebExchangeBindException.class)
    public ResponseEntity handleWebExchangeBindException(WebExchangeBindException e) {
        log.error(e.getMessage());

        return new ResponseEntity<String>(toStr(e), HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(CheckException.class)
    public ResponseEntity handleCheckException(CheckException e) {
        log.error(e.getMessage());
        return new ResponseEntity<String>(toStr(e), HttpStatus.BAD_REQUEST);
    }

    private String toStr(CheckException e) {
        return e.getFiledName() + " : " + e.getFiledValue();
    }

    /**
     * 把校验异常转化成字符串
     * @param e
     * @return
     */
    private String toStr(WebExchangeBindException e) {
        return e.getFieldErrors().stream()
                .map(error -> error.getField() + " : " + error.getDefaultMessage())
                .reduce("", (s1, s2) -> s1 + " \n " + s2);
    }
}
CheckAdvice.java

    5 .5.3 自定义校验方法

package cn.xiangxu.webflux_test.util;

import cn.xiangxu.webflux_test.exception.CheckException;

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

/**
 * @author 王杨帅
 * @create 2018-08-02 17:00
 * @desc 检查工具类
 **/
public class CheckUtil {

    private static final String[] INVALID_NAMES = {"admin", "fury"};

    /**
     * 校验名字:不成功时抛出自定义异常
     * @param value
     */
    public static void checkeName(String value) {
        Stream.of(INVALID_NAMES)
                .filter(name -> name.equalsIgnoreCase(value))
                .findAny()
                .ifPresent(
                        name -> {
                            throw new CheckException("name", value);
                        }
                );
    }

}
CheckUtil.java

    5.5.4 使用自定义参数校验方法

package cn.xiangxu.webflux_test.controller;

import cn.xiangxu.webflux_test.domain.domain_do.StudentDO;
import cn.xiangxu.webflux_test.reposigory.StudentRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.validation.Valid;

import static cn.xiangxu.webflux_test.util.CheckUtil.checkeName;

/**
 * @author 王杨帅
 * @create 2018-08-02 15:44
 * @desc 学生控制层
 **/
@RestController
@RequestMapping(value = "/stu")
@Slf4j
public class StudentController {

    @Autowired
    private StudentRepository studentRepository;

    @GetMapping
    public Flux<StudentDO> findList() {
        return studentRepository.findAll();
    }

    @PostMapping
    public Mono<StudentDO> create(
            @Valid @RequestBody StudentDO studentDO) {
        System.out.println(studentDO);
        checkeName(studentDO.getName());
        return studentRepository.save(studentDO);
    }

    @PutMapping()
    public Mono<ResponseEntity<String>> update(
            @Valid @RequestBody StudentDO studentDO

    ) {
        log.info("前端传过来的数据为:" + studentDO);
        checkeName(studentDO.getName());
        return studentRepository.findById(studentDO.getId())
                .flatMap(student -> studentRepository.save(studentDO))
                .map(student -> new ResponseEntity<String>("更新成功", HttpStatus.OK))
                .defaultIfEmpty(new ResponseEntity<String>("ID不合法", HttpStatus.BAD_REQUEST));
    }

    @DeleteMapping(value = "/{id}")
    public Mono<ResponseEntity<Void>> delete(
            @PathVariable(value = "id") String id
    ) {
        log.info("从前端获取到的ID信息为:" + id);
        return studentRepository.findById(id)
                .flatMap(
                        student -> {
                            return studentRepository.deleteById(student.getId())
                                    .then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)));
                        }
                )
                .defaultIfEmpty(new ResponseEntity<Void>(HttpStatus.NOT_FOUND));
    }

    @GetMapping(value = "/{id}")
    public Mono<ResponseEntity<StudentDO>> findById(
            @PathVariable("id") String id
    ) {
        log.info("从前端获取到的参数信息为:" + id);
        return studentRepository.findById(id)
                .map(student -> new ResponseEntity<StudentDO>(student, HttpStatus.OK))
                .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
    }

}
StudentController.java

 

6 SpringBootWebflux 整合 MongoDB 实现 CRUD 参考代码

  

  Spring Boot 2.0 的 WebFlux

Spring Boot 2.0 的 WebFlux

  Spring Boot 2.0 的 WebFlux

 

对照下 Spring Web MVC ,Spring Web MVC 是基于 Servlet API 和 Servlet 容器设计的。那么 Spring WebFlux 肯定不是基于前面两者,它基于 Reactive Streams API 和 Servlet 3.1+ 容器设计。

那 Reactive Streams API 是什么?

先理解 Stream 流是什么?流是序列,是生产者生产,一个或多个消费者消费的元素序列。这种具体的设计模式成为发布订阅模式。常见的流处理机制是 pull /push 模式。背压是一种常用策略,使得发布者拥有无限制的缓冲区存储 item,用于确保发布者发布 item 太快时,不会去压制订阅者。
Reactive Streams (响应式流)是提供处理非阻塞背压异步流的一种标准。主要针对的场景是运行时环境(包括 JVM 和 JS)和网络。同样,JDK 9 java.util.concurrent 包提供了两个主要的 API 来处理响应流:
- Flow
- SubmissionPublisher

为啥只能运行在 Servlet 3.1+ 容器?

大家知道,3.1 规范其中一个新特性是异步处理支持。
异步处理支持:Servlet 线程不需一直阻塞,即不需要到业务处理完毕再输出响应,然后结束 Servlet 线程。异步处理的作用是在接收到请求之后,Servlet 线程可以将耗时的操作委派给另一个线程来完成,在不生成响应的情况下返回至容器。主要应用场景是针对业务处理较耗时的情况,可以减少服务器资源的占用,并且提高并发处理速度。
所以 WebFlux 支持的容器有 Tomcat、Jetty(Non-Blocking IO API) ,也可以像 Netty 和 Undertow 的本身就支持异步容器。在容器中 Spring WebFlux 会将输入流适配成 Mono 或者 Flux 格式进行统一处理。

Spring WebFlux 是什么

先看这张图,上面我们了解了容器、响应流。这里介绍下 Spring WebFlux 是什么? Spring WebFlux 是 Spring 5 的一个新模块,包含了响应式 HTTP 和 WebSocket 的支持,另外在上层服务端支持两种不同的编程模型:
- 基于 Spring MVC 注解 @Controller 等
- 基于 Functional 函数式路由

下面是两个实现小案例,首先在 pom.xml 加入对应的依赖:

   <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>

基于 Spring MVC 注解 RESTful API

官方案例很简单,如下:

@RestController
public class PersonController {

        private final PersonRepository repository;

        public PersonController(PersonRepository repository) {
                this.repository = repository;
        }

        @PostMapping("/person")
        Mono<Void> create(@RequestBody Publisher<Person> personStream) {
                return this.repository.save(personStream).then();
        }

        @GetMapping("/person")
        Flux<Person> list() {
                return this.repository.findAll();
        }

        @GetMapping("/person/{id}")
        Mono<Person> findById(@PathVariable String id) {
                return this.repository.findOne(id);
        }
}

但是 PersonRepository 这种 Spring Data Reactive Repositories 不支持 MySQL,进一步也不支持 MySQL 事务。所以用了 Reactivey 原来的 spring 事务管理就不好用了。jdbc jpa 的事务是基于阻塞 IO 模型的,如果 Spring Data Reactive 没有升级 IO 模型去支持 JDBC,生产上的应用只能使用不强依赖事务的。也可以使用透明的事务管理,即每次操作的时候以回调形式去传递数据库连接 connection。

Spring Data Reactive Repositories 目前支持 Mongo、Cassandra、Redis、Couchbase 。

那提到的 “用了 Reactivey 原来的 spring 事务管理就不好用了”,您能否再详细介绍一下?另外,应用有强依赖事务,有没有对应的解决方案?

我们先看看这张图。Spring Boot 2.0 这里有两条不同的线分别是:
Spring Web MVC -> Spring Data
Spring WebFlux -> Spring Data Reactive

所以这里问题的答案是,如果使用 Spring Data Reactive ,原来的 Spring 针对 Spring Data (JDBC 等)的事务管理肯定不起作用了。因为原来的 Spring 事务管理(Spring Data JPA)都是基于 ThreadLocal 传递事务的,其本质是基于 阻塞 IO 模型,不是异步的。但 Reactive 是要求异步的,不同线程里面 ThreadLocal 肯定取不到值了。自然,我们得想想如何在使用 Reactive 编程是做到事务,有一种方式是 回调 方式,一直传递 conn :
newTransaction(conn ->{})

因为每次操作数据库也是异步的,所以 connection 在 Reactive 编程中无法靠 ThreadLocal 传递了,只能放在参数上面传递。虽然会有一定的代码侵入行。进一步,也可以 kotlin 协程,去做到透明的事务管理,即把 conn 放到 协程的局部变量中去。
那 Spring Data Reactive Repositories 不支持 MySQL,进一步也不支持 MySQL 事务,怎么办?

答案是,这个问题其实和第一个问题也相关。 为啥不支持 MySQL,即 JDBC 不支持。大家可以看到 JDBC 是所属 Spring Data 的。所以可以等待 Spring Data Reactive Repositories 升级 IO 模型,去支持 MySQL。也可以和上面也讲到了,如何使用 Reactive 编程支持事务。

如果应用只能使用不强依赖数据事务,依旧使用 MySQL ,可以使用下面的实现,代码如下:

@RestController
@RequestMapping(value = "/city")
public class CityRestController {

    @Autowired
    private CityService cityService;

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public Mono<City> findOneCity(@PathVariable("id") Long id) {
        return Mono.create(cityMonoSink -> cityMonoSink.success(cityService.findCityById(id)));
    }

    @RequestMapping(method = RequestMethod.GET)
    public Flux<City> findAllCity() {
        return Flux.create(cityFluxSink -> {
            cityService.findAllCity().forEach(city -> {
                cityFluxSink.next(city);
            });
            cityFluxSink.complete();
        });
    }

    @RequestMapping(method = RequestMethod.POST)
    public Mono<Long> createCity(@RequestBody City city) {
        return Mono.create(cityMonoSink -> cityMonoSink.success(cityService.saveCity(city)));
    }

    @RequestMapping(method = RequestMethod.PUT)
    public Mono<Long> modifyCity(@RequestBody City city) {
        return Mono.create(cityMonoSink -> cityMonoSink.success(cityService.updateCity(city)));
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    public Mono<Long> modifyCity(@PathVariable("id") Long id) {
        return Mono.create(cityMonoSink -> cityMonoSink.success(cityService.deleteCity(id)));
    }
}

findAllCity 方法中,利用 Flux.create 方法对响应进行创建封装成 Flux 数据。并且使用 lambda 写数据流的处理函数会十分的方便。
Service 层依旧是以前那套逻辑,业务服务层接口如下:

public interface CityService {

    /**
     * 获取城市信息列表
     *
     * @return
     */
    List<City> findAllCity();

    /**
     * 根据城市 ID,查询城市信息
     *
     * @param id
     * @return
     */
    City findCityById(Long id);

    /**
     * 新增城市信息
     *
     * @param city
     * @return
     */
    Long saveCity(City city);

    /**
     * 更新城市信息
     *
     * @param city
     * @return
     */
    Long updateCity(City city);

    /**
     * 根据城市 ID,删除城市信息
     *
     * @param id
     * @return
     */
    Long deleteCity(Long id);
}

具体案例在我的 Github:https://github.com/JeffLi1993/springboot-learning-example

基于 Functional 函数式路由实现 RESTful API

创建一个 Route 类来定义 RESTful HTTP 路由

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.route;

@Configuration
public class Routes {
    private CityService cityService;

    public Routes(CityService cityService) {
        this.cityService = cityService;
    }

    @Bean
    public RouterFunction<?> routerFunction() {
        return route(
                       GET("/api/city").and(accept(MediaType.APPLICATION_JSON)), cityService:: findAllCity).and(route(
                       GET("/api/user/{id}").and(accept(MediaType.APPLICATION_JSON)), cityService:: findCityById)
                );
    }
}

RoouterFunction 类似 Spring Web MVC 的 @RequestMapping ,用来定义路由信息,每个路由会映射到一个处理方法,当接受 HTTP 请求时候会调用该处理方法。

创建 HttpServerConfig 自定义 Http Server,这里创建一个 Netty HTTP 服务器:

import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import reactor.ipc.netty.http.server.HttpServer;

@Configuration
public class HttpServerConfig {
    @Autowired
    private Environment environment;

    @Bean
    public HttpServer httpServer(RouterFunction<?> routerFunction) {
        HttpHandler httpHandler = RouterFunctions.toHttpHandler(routerFunction);
        ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
        HttpServer server = HttpServer.create("localhost", Integer.valueOf(environment.getProperty("server.port")));
        server.newHandler(adapter);
        return server;
    }
}

自然推荐 Netty 来运行 Reactive 应用,因为 Netty 是基于异步和事件驱动的。

java-带@EnableWebFlux批注的SpringWebFlux错误

java-带@EnableWebFlux批注的SpringWebFlux错误

我正在使用Spring Boot 2.1.1版本并使用@EnableWebFlux,但出现了一些错误.

错误是

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.reactive.config.DelegatingWebFluxConfiguration': Initialization of bean Failed; nested exception is java.lang.IllegalStateException: The Java/XML config for Spring MVC and Spring WebFlux cannot both be enabled,e.g. via @EnableWebMvc and @EnableWebFlux,in the same application

我该如何解决此问题.

最佳答案
您不能同时在Spring SPR-16609中启用Spring MVC和Spring WebFlux

enabling MVC and WebFlux in the same application context which triggers a conflict

you can’t have them in the same process currently.

它提供了使用反应式存储库的解决方法:

However,you can use reactive repositories from your existing Spring MVC application,and return the reactive types (Flux or Mono),from Spring MVC controller methods.

Spring Boot 2 快速教程:WebFlux 集成 Mongodb(四)

Spring Boot 2 快速教程:WebFlux 集成 Mongodb(四)

摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢!

这是泥瓦匠的第104篇原创

文章工程:

  • JDK 1.8
  • Maven 3.5.2
  • Spring Boot 2.1.3.RELEASE
  • 工程名:springboot-webflux-4-thymeleaf
  • 工程地址:见文末

一、前言

上一讲用 Map 数据结构内存式存储了数据。这样数据就不会持久化,本文我们用 MongoDB 来实现 WebFlux 对数据源的操作。

什么是 MongoDB ?

官网:https://www.mongodb.com/

MongoDB 是一个基于分布式文件存储的数据库,由 C++ 语言编写,旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。

MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。

由于操作方便,本文用 Docker 启动一个 MognoDB 服务。如果 Docker 不会安装的,请参考此文:Docker 安装与基本操作 https://www.jianshu.com/p/f27...

Docker 安装 MognoDB 并启动如下:

1、创建挂载目录

docker volume create mongo_data_db
docker volume create mongo_data_configdb

2、启动 MognoDB

docker run -d \
    --name mongo \
    -v mongo_data_configdb:/data/configdb \
    -v mongo_data_db:/data/db \
    -p 27017:27017 \
    mongo \
    --auth

3、初始化管理员账号

docker exec -it mongo     mongo              admin
                        // 容器名   // mongo命令 数据库名

# 创建最高权限用户
db.createUser({ user: ''admin'', pwd: ''admin'', roles: [ { role: "root", db: "admin" } ] });

4、测试连通性

docker run -it --rm --link mongo:mongo mongo mongo -u admin -p admin --authenticationDatabase admin mongo/admin

MognoDB 基本操作:

类似 MySQL 命令,显示库列表:

show dbs

使用某数据库

use admin

显示表列表

show collections

如果存在 city 表,格式化显示 city 表内容

db.city.find().pretty()

二、结构

类似上面讲的工程搭建,新建一个工程编写此案例。工程如图:

[图片上传失败...(image-df7c3f-1558613654501)]

目录核心如下

  • pom.xml maven 配置
  • application.properties 配置文件
  • dao 数据访问层,本文要点

三、新增 POM 依赖与配置

在 pom.xml 配置新的依赖:

    <!-- Spring Boot 响应式 MongoDB 依赖 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
    </dependency>

类似配了 MySQL 和 JDBC 驱动,肯定得去配置数据库。在 application.properties 配置下上面启动的 MongoDB 配置:

数据库名为 admin、账号密码也为 admin。

spring.data.mongodb.host=localhost
spring.data.mongodb.database=admin
spring.data.mongodb.port=27017
spring.data.mongodb.username=admin
spring.data.mongodb.password=admin

这就一个巨大的问题了,为啥不用我们常用的 MySQL 数据库呢?

答案是 Spring Data Reactive Repositories 目前支持 Mongo、Cassandra、Redis、Couchbase。不支持 MySQL ,那究竟为啥呢?那就说明下 JDBC 和 Spring Data 的关系。

Spring Data Reactive Repositories 突出点是 Reactive,即非阻塞的。区别如下:

  • 基于 JDBC 实现的 Spring Data ,比如 Spring Data JPA 是阻塞的。原理是基于阻塞 IO 模型

消耗每个调用数据库的线程(Connection)

  • 事务只能在一个 java.sql.Connection 使用,即一个事务一个操作。

那如何异步非阻塞封装下 JDBC 的思想也不新鲜,Scala 库 Slick 3 就实现了。简单的实现原理如下:

  • 一个事务多个操作,那么共享一个 java.sql.Connection 。可以使用透明事务管理,利用回调编程模型去传递
  • 保持有限的空闲连接

最后,我坚信非阻塞 JDBC 很快就会出现的。这样我们就开心的调用 MySQL 了。

四、对象

修改 org.spring.springboot.domain 包里面的城市实体对象类。修改城市(City)对象 City,代码如下:

import org.springframework.data.annotation.Id;

/**
 * 城市实体类
 *
 */
public class City {

    /**
     * 城市编号
     */
    @Id
    private Long id;

    /**
     * 省份编号
     */
    private Long provinceId;

    /**
     * 城市名称
     */
    private String cityName;

    /**
     * 描述
     */
    private String description;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getProvinceId() {
        return provinceId;
    }

    public void setProvinceId(Long provinceId) {
        this.provinceId = provinceId;
    }

    public String getCityName() {
        return cityName;
    }

    public void setCityName(String cityName) {
        this.cityName = cityName;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

@Id 注解标记对应库表的主键或者唯一标识符。因为这个是我们的 DO ,数据访问对象一一映射到数据存储。

五、MongoDB 数据访问层 CityRepository

修改 CityRepository 类,代码如下:

import org.spring.springboot.domain.City;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CityRepository extends ReactiveMongoRepository<City, Long> {

}

CityRepository 接口只要继承 ReactiveMongoRepository 类即可。默认会提供很多实现,比如 CRUD 和列表查询参数相关的实现。ReactiveMongoRepository 接口默认实现了如下:

    <S extends T> Mono<S> insert(S var1);

    <S extends T> Flux<S> insert(Iterable<S> var1);

    <S extends T> Flux<S> insert(Publisher<S> var1);

    <S extends T> Flux<S> findAll(Example<S> var1);

    <S extends T> Flux<S> findAll(Example<S> var1, Sort var2);

如图,ReactiveMongoRepository 的集成类 ReactiveSortingRepository、ReactiveCrudRepository 实现了很多常用的接口:

[图片上传失败...(image-d465a3-1558613654501)]

ReactiveCrudRepository 接口如图所示:
[图片上传失败...(image-5aa4b4-1558613654501)]

另外可以看出,接口的命名是遵循规范的。常用命名规则如下:

  • 关键字 :: 方法命名
  • And :: findByNameAndPwd
  • Or :: findByNameOrSex
  • Is :: findById
  • Between :: findByIdBetween
  • Like :: findByNameLike
  • NotLike :: findByNameNotLike
  • OrderBy :: findByIdOrderByXDesc
  • Not :: findByNameNot

常用案例,代码如下:

    Flux<Person> findByLastname(String lastname);

    @Query("{ ''firstname'': ?0, ''lastname'': ?1}")
    Mono<Person> findByFirstnameAndLastname(String firstname, String lastname);

    // Accept parameter inside a reactive type for deferred execution
    Flux<Person> findByLastname(Mono<String> lastname);

    Mono<Person> findByFirstnameAndLastname(Mono<String> firstname, String lastname);

    @Tailable // Use a tailable cursor
    Flux<Person> findWithTailableCursorBy();

5.1 源码层面

ReactiveCrudRepository 抽象在 reactive 包,如图:

[图片上传失败...(image-9c90fc-1558613654501)]

这里我们可以看出,支持了 reactive 还支持了 RxJava。对应老的 CrudRepository 新增了 ReactiveCrudRepository 接口及各种存储实现。

六、处理器类 Handler 和控制器类 Controller

修改下 Handler ,代码如下:

@Component
public class CityHandler {

    private final CityRepository cityRepository;

    @Autowired
    public CityHandler(CityRepository cityRepository) {
        this.cityRepository = cityRepository;
    }

    public Mono<City> save(City city) {
        return cityRepository.save(city);
    }

    public Mono<City> findCityById(Long id) {

        return cityRepository.findById(id);
    }

    public Flux<City> findAllCity() {

        return cityRepository.findAll();
    }

    public Mono<City> modifyCity(City city) {

        return cityRepository.save(city);
    }

    public Mono<Long> deleteCity(Long id) {
        cityRepository.deleteById(id);
        return Mono.create(cityMonoSink -> cityMonoSink.success(id));
    }
}

不要对 Mono 、Flux 陌生,把他当成对象即可。继续修改下控制器类 Controller ,代码如下:

@RestController
@RequestMapping(value = "/city")
public class CityWebFluxController {

    @Autowired
    private CityHandler cityHandler;

    @GetMapping(value = "/{id}")
    public Mono<City> findCityById(@PathVariable("id") Long id) {
        return cityHandler.findCityById(id);
    }

    @GetMapping()
    public Flux<City> findAllCity() {
        return cityHandler.findAllCity();
    }

    @PostMapping()
    public Mono<City> saveCity(@RequestBody City city) {
        return cityHandler.save(city);
    }

    @PutMapping()
    public Mono<City> modifyCity(@RequestBody City city) {
        return cityHandler.modifyCity(city);
    }

    @DeleteMapping(value = "/{id}")
    public Mono<Long> deleteCity(@PathVariable("id") Long id) {
        return cityHandler.deleteCity(id);
    }
}

七、运行工程

一个 CRUD 的 Spring Boot Webflux 工程就开发完毕了,下面运行工程验证下。使用 IDEA 右侧工具栏,点击 Maven Project Tab ,点击使用下 Maven 插件的 install 命令。或者使用命令行的形式,在工程根目录下,执行 Maven 清理和安装工程的指令:

cd springboot-webflux-3-mongodb
mvn clean install

在控制台中看到成功的输出:

... 省略
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:30 min
[INFO] Finished at: 2017-10-15T10:00:54+08:00
[INFO] Final Memory: 31M/174M
[INFO] ------------------------------------------------------------------------

在 IDEA 中执行 Application 类启动,任意正常模式或者 Debug 模式。可以在控制台看到成功运行的输出:

... 省略
2018-04-10 08:43:39.932  INFO 2052 --- [ctor-http-nio-1] r.ipc.netty.tcp.BlockingNettyContext     : Started HttpServer on /0:0:0:0:0:0:0:0:8080
2018-04-10 08:43:39.935  INFO 2052 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8080
2018-04-10 08:43:39.960  INFO 2052 --- [           main] org.spring.springboot.Application        : Started Application in 6.547 seconds (JVM running for 9.851)

打开 POST MAN 工具,开发必备。进行下面操作:

新增城市信息 POST http://127.0.0.1:8080/city

7.1 连接 MongoDB , 验证数据

连接 MongoDB

docker run -it --rm --link mongo:mongo mongo mongo -u admin -p admin --authenticationDatabase admin mongo/admin

显示库列表:

show dbs

使用某数据库

use admin

显示表列表

show collections

如果存在 city 表,格式化显示 city 表内容:

db.city.find().pretty()

八、总结

这里,探讨了 Spring WebFlux 的如何整合 MongoDB 。整合其他存储 Cassandra、Redis、Couchbase,就大同小异了。下面,我们能会整合 Thymeleaf,更好的页面展示给大家。顺便让大家学习下 Thymeleaf 的基本用法。

系列教程目录

  • 《01:WebFlux 系列教程大纲》
  • 《02:WebFlux 快速入门实践》
  • 《03:WebFlux Web CRUD 实践》
  • 《04:WebFlux 整合 Mongodb》
  • 《05:WebFlux 整合 Thymeleaf》
  • 《06:WebFlux 中 Thymeleaf 和 Mongodb 实践》
  • 《07:WebFlux 整合 Redis》
  • 《08:WebFlux 中 Redis 实现缓存》
  • 《09:WebFlux 中 WebSocket 实现通信》
  • 《10:WebFlux 集成测试及部署》
  • 《11:WebFlux 实战图书管理系统》

代码示例

本文示例读者可以通过查看下面仓库的中的模块工程名: 2-x-spring-boot-webflux-handling-errors:

  • Github:https://github.com/JeffLi1993/springboot-learning-example
  • Gitee:https://gitee.com/jeff1993/springboot-learning-example

如果您对这些感兴趣,欢迎 star、follow、收藏、转发给予支持!

参考资料

  • Spring Boot 2.x WebFlux 系列:https://www.bysocket.com/arch...
  • spring.io 官方文档

以下专题教程也许您会有兴趣

  • 《程序兵法:算法与数据结构》 https://www.bysocket.com/arch...
  • 《Spring Boot 2.x 系列教程》

https://www.bysocket.com/spri...

  • 《Java 核心系列教程》

https://www.bysocket.com/arch...
image.png

Spring Boot 2.0 WebFlux Web CRUD 实践

Spring Boot 2.0 WebFlux Web CRUD 实践

03:WebFlux Web CRUD 实践

前言

上一篇基于功能性端点去创建一个简单服务,实现了 Hello 。这一篇用 Spring Boot WebFlux 的注解控制层技术创建一个 CRUD WebFlux 应用,让开发更方便。这里我们不对数据库储存进行访问,因为后续会讲到,而且这里主要是讲一个完整的 WebFlux CRUD。

结构

这个工程会对城市(City)进行管理实现 CRUD 操作。该工程创建编写后,得到下面的结构,其目录结构如下:

├── pom.xml
├── src
│   └── main
│       ├── java
│       │   └── org
│       │       └── spring
│       │           └── springboot
│       │               ├── Application.java
│       │               ├── dao
│       │               │   └── CityRepository.java
│       │               ├── domain
│       │               │   └── City.java
│       │               ├── handler
│       │               │   └── CityHandler.java
│       │               └── webflux
│       │                   └── controller
│       │                       └── CityWebFluxController.java
│       └── resources
│           └── application.properties
└── target

如目录结构,我们需要编写的内容按顺序有:

  • 对象
  • 数据访问层类 Repository
  • 处理器类 Handler
  • 控制器类 Controller

对象

新建包 org.spring.springboot.domain ,作为编写城市实体对象类。新建城市(City)对象 City,代码如下:

/**
 * 城市实体类
 *
 */
public class City {

    /**
     * 城市编号
     */
    private Long id;

    /**
     * 省份编号
     */
    private Long provinceId;

    /**
     * 城市名称
     */
    private String cityName;

    /**
     * 描述
     */
    private String description;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getProvinceId() {
        return provinceId;
    }

    public void setProvinceId(Long provinceId) {
        this.provinceId = provinceId;
    }

    public String getCityName() {
        return cityName;
    }

    public void setCityName(String cityName) {
        this.cityName = cityName;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

城市包含了城市编号、省份编号、城市名称和描述。具体开发中,会使用 Lombok 工具来消除冗长的 Java 代码,尤其是 POJO 的 getter / setter 方法。具体查看 Lombok 官网地址:projectlombok.org。

数据访问层 CityRepository

新建包 org.spring.springboot.dao ,作为编写城市数据访问层类 Repository。新建 CityRepository,代码如下:

import org.spring.springboot.domain.City;
import org.springframework.stereotype.Repository;

import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;

@Repository
public class CityRepository {

    private ConcurrentMap<Long, City> repository = new ConcurrentHashMap<>();

    private static final AtomicLong idGenerator = new AtomicLong(0);

    public Long save(City city) {
        Long id = idGenerator.incrementAndGet();
        city.setId(id);
        repository.put(id, city);
        return id;
    }

    public Collection<City> findAll() {
        return repository.values();
    }


    public City findCityById(Long id) {
        return repository.get(id);
    }

    public Long updateCity(City city) {
        repository.put(city.getId(), city);
        return city.getId();
    }

    public Long deleteCity(Long id) {
        repository.remove(id);
        return id;
    }
}

@Repository 用于标注数据访问组件,即 DAO 组件。实现代码中使用名为 repository 的 Map 对象作为内存数据存储,并对对象具体实现了具体业务逻辑。CityRepository 负责将 Book 持久层(数据操作)相关的封装组织,完成新增、查询、删除等操作。

这里不会涉及到数据存储这块,具体数据存储会在后续介绍。

处理器类 Handler

新建包 org.spring.springboot.handler ,作为编写城市处理器类 CityHandler。新建 CityHandler,代码如下:

import org.spring.springboot.dao.CityRepository;
import org.spring.springboot.domain.City;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Component
public class CityHandler {

    private final CityRepository cityRepository;

    @Autowired
    public CityHandler(CityRepository cityRepository) {
        this.cityRepository = cityRepository;
    }

    public Mono<Long> save(City city) {
        return Mono.create(cityMonoSink -> cityMonoSink.success(cityRepository.save(city)));
    }

    public Mono<City> findCityById(Long id) {
        return Mono.justOrEmpty(cityRepository.findCityById(id));
    }

    public Flux<City> findAllCity() {
        return Flux.fromIterable(cityRepository.findAll());
    }

    public Mono<Long> modifyCity(City city) {
        return Mono.create(cityMonoSink -> cityMonoSink.success(cityRepository.updateCity(city)));
    }

    public Mono<Long> deleteCity(Long id) {
        return Mono.create(cityMonoSink -> cityMonoSink.success(cityRepository.deleteCity(id)));
    }
}

@Component 泛指组件,当组件不好归类的时候,使用该注解进行标注。然后用 final@Autowired 标注在构造器注入 CityRepository Bean,代码如下:

    private final CityRepository cityRepository;

    @Autowired
    public CityHandler(CityRepository cityRepository) {
        this.cityRepository = cityRepository;
    }

从返回值可以看出,Mono 和 Flux 适用于两个场景,即:

  • Mono:实现发布者,并返回 0 或 1 个元素,即单对象
  • Flux:实现发布者,并返回 N 个元素,即 List 列表对象

有人会问,这为啥不直接返回对象,比如返回 City/Long/List。原因是,直接使用 Flux 和 Mono 是非阻塞写法,相当于回调方式。利用函数式可以减少了回调,因此会看不到相关接口。这恰恰是 WebFlux 的好处:集合了非阻塞 + 异步。

Mono

Mono 是什么? 官方描述如下:A Reactive Streams Publisher with basic rx operators that completes successfully by emitting an element, or with an error.

Mono 是响应流 Publisher 具有基础 rx 操作符。可以成功发布元素或者错误。如图所示:

file

Mono 常用的方法有:

  • Mono.create():使用 MonoSink 来创建 Mono
  • Mono.justOrEmpty():从一个 Optional 对象或 null 对象中创建 Mono。
  • Mono.error():创建一个只包含错误消息的 Mono
  • Mono.never():创建一个不包含任何消息通知的 Mono
  • Mono.delay():在指定的延迟时间之后,创建一个 Mono,产生数字 0 作为唯一值

Flux

Flux 是什么? 官方描述如下:A Reactive Streams Publisher with rx operators that emits 0 to N elements, and then completes (successfully or with an error).

Flux 是响应流 Publisher 具有基础 rx 操作符。可以成功发布 0 到 N 个元素或者错误。Flux 其实是 Mono 的一个补充。如图所示:

file

所以要注意:如果知道 Publisher 是 0 或 1 个,则用 Mono。

Flux 最值得一提的是 fromIterable 方法。 fromIterable(Iterable<? extends T> it) 可以发布 Iterable 类型的元素。当然,Flux 也包含了基础的操作:map、merge、concat、flatMap、take,这里就不展开介绍了。

控制器类 Controller

Spring Boot WebFlux 开发中,不需要配置。Spring Boot WebFlux 可以使用自动配置加注解驱动的模式来进行开发。

新建包目录 org.spring.springboot.webflux.controller ,并在目录中创建名为 CityWebFluxController 来处理不同的 HTTP Restful 业务请求。代码如下:

import org.spring.springboot.domain.City;
import org.spring.springboot.handler.CityHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@RestController
@RequestMapping(value = "/city")
public class CityWebFluxController {

    @Autowired
    private CityHandler cityHandler;

    @GetMapping(value = "/{id}")
    public Mono<City> findCityById(@PathVariable("id") Long id) {
        return cityHandler.findCityById(id);
    }

    @GetMapping()
    public Flux<City> findAllCity() {
        return cityHandler.findAllCity();
    }

    @PostMapping()
    public Mono<Long> saveCity(@RequestBody City city) {
        return cityHandler.save(city);
    }

    @PutMapping()
    public Mono<Long> modifyCity(@RequestBody City city) {
        return cityHandler.modifyCity(city);
    }

    @DeleteMapping(value = "/{id}")
    public Mono<Long> deleteCity(@PathVariable("id") Long id) {
        return cityHandler.deleteCity(id);
    }
}

这里按照 REST 风格实现接口。那具体什么是 REST?

REST 是属于 WEB 自身的一种架构风格,是在 HTTP 1.1 规范下实现的。Representational State Transfer 全称翻译为表现层状态转化。Resource:资源。比如 newsfeed;Representational:表现形式,比如用JSON,富文本等;State Transfer:状态变化。通过HTTP 动作实现。

理解 REST ,要明白五个关键要素:

  • 资源(Resource)
  • 资源的表述(Representation)
  • 状态转移(State Transfer)
  • 统一接口(Uniform Interface)
  • 超文本驱动(Hypertext Driven)

6 个主要特性:

  • 面向资源(Resource Oriented)
  • 可寻址(Addressability)
  • 连通性(Connectedness)
  • 无状态(Statelessness)
  • 统一接口(Uniform Interface)
  • 超文本驱动(Hypertext Driven)

具体这里就不一一展开,详见 http://www.infoq.com/cn/artic...。

请求入参、Filters、重定向、Conversion、formatting 等知识会和以前 MVC 的知识一样,详情见文档:
https://docs.spring.io/spring...

运行工程

一个 CRUD 的 Spring Boot Webflux 工程就开发完毕了,下面运行工程验证下。使用 IDEA 右侧工具栏,点击 Maven Project Tab ,点击使用下 Maven 插件的 install 命令。或者使用命令行的形式,在工程根目录下,执行 Maven 清理和安装工程的指令:

cd springboot-webflux-2-restful
mvn clean install

在控制台中看到成功的输出:

... 省略
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:30 min
[INFO] Finished at: 2017-10-15T10:00:54+08:00
[INFO] Final Memory: 31M/174M
[INFO] ------------------------------------------------------------------------

在 IDEA 中执行 Application 类启动,任意正常模式或者 Debug 模式。可以在控制台看到成功运行的输出:

... 省略
2018-04-10 08:43:39.932  INFO 2052 --- [ctor-http-nio-1] r.ipc.netty.tcp.BlockingNettyContext     : Started HttpServer on /0:0:0:0:0:0:0:0:8080
2018-04-10 08:43:39.935  INFO 2052 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8080
2018-04-10 08:43:39.960  INFO 2052 --- [           main] org.spring.springboot.Application        : Started Application in 6.547 seconds (JVM running for 9.851)

打开 POST MAN 工具,开发必备。进行下面操作:

新增城市信息 POST http://127.0.0.1:8080/city

file

获取城市信息列表 GET http://127.0.0.1:8080/city

file

其他接口就不演示了。

总结

这里,探讨了 Spring WebFlux 的一些功能,构建没有底层数据库的基本 CRUD 工程。为了更好的展示了如何创建 Flux 流,以及如何对其进行操作。下面会讲到如何操作数据存储。

本文由博客群发一文多发等运营工具平台 OpenWrite 发布

我们今天的关于WebFlux04 SpringBootWebFlux 集成 MongoDB 之 Windows 版本、WebFlux 实现 CRUD、WebFlux 实现 JPA、参数校验的分享就到这里,谢谢您的阅读,如果想了解更多关于 Spring Boot 2.0 的 WebFlux、java-带@EnableWebFlux批注的SpringWebFlux错误、Spring Boot 2 快速教程:WebFlux 集成 Mongodb(四)、Spring Boot 2.0 WebFlux Web CRUD 实践的相关信息,可以在本站进行搜索。

本文标签: