SpringBoot2.0 WebFlux響應式編程

一、什麼是WebFlux?

在這裏插入圖片描述

  • WebFlux是異步非阻塞的,SpringMVC是同步阻塞的
  • 響應式一般用Netty或者Servlet 3.1的容器(因爲支持異步非阻塞),而Servlet技術棧用的是Servlet容器

二、基於Spring MVC註解的方式

1、添加依賴

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

2、案例

  • Mono表示的是包含0或者1個元素的異步序列
  • Flux表示的是包含0到N個元素的異步序列
@RestController
@RequestMapping("/api")
public class WebfluxDemoController {
    @GetMapping("/mono")
    public Mono<String> mono() {
        return Mono.just("hello world");
    }

    @GetMapping(value = "/flux", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> flux() {
        return Flux.fromStream(IntStream.range(1, 5).mapToObj(i -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "hello--" + i;
        }));
    }
}

應用啓動後發現應用運行於Netty上

在這裏插入圖片描述

瀏覽器訪問http://localhost:8080/api/flux,可以看到Flux可以多次返回數據(通過SSE實現)

3、SSE(Server-Sent Events)

嚴格地說,HTTP協議無法做到服務器主動推送信息。但是,有一種變通方法,就是服務器向客戶端聲明,接下來要發送的是流信息

也就是說,發送的不是一次性的數據包,而是一個數據流,會連續不斷地發送過來。這時,客戶端不會關閉連接,會一直等着服務器發過來的新的數據流,視頻播放就是這樣的例子。本質上,這種通信就是以流信息的方式,完成一次用時很長的下載

SSE就是利用這種機制,使用流信息向瀏覽器推送信息。它基於HTTP協議,目前除了IE/Edge,其他瀏覽器都支持

4、整合MongoDB

1)、添加依賴

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
        </dependency>

2)、主啓動類添加@EnableReactiveMongoRepositories註解

3)、添加mongodb配置信息

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

4)、實現代碼

@Document(collection = "user")
@Data
public class User {
    @Id
    private String id;

    private String name;

    private Integer age;
}
@Repository
public interface UserRepository extends ReactiveMongoRepository<User, String> {

}
@RestController
@RequestMapping("/api/user")
public class UserController {

    private final UserRepository repository;

    public UserController(UserRepository repository) {
        this.repository = repository;
    }

    /**
     * 以數組形式一次性返回數據
     *
     * @return
     */
    @GetMapping
    public Flux<User> getAllUser() {
        return repository.findAll();
    }

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

    /**
     * 新增用戶信息
     *
     * @param user
     * @return
     */
    @PostMapping
    public Mono<User> createUser(@RequestBody User user) {
        //Spring Data Jpa裏面,新增和修改都是save,有id是修改,id爲空是新增
        user.setId(null);
        return this.repository.save(user);
    }

    /**
     * 根據ID刪除用戶信息
     *
     * @param id
     * @return
     */
    @DeleteMapping("/{id}")
    public Mono<ResponseEntity<Void>> deleteUserById(@PathVariable String id) {
        //deleteById沒有返回值,不能判斷數據是否存在
        //this.repository.deleteById(id);
        return this.repository.findById(id)
                //當需要操作數據,並返回一個Mono 這時候使用flatMap
                //如果不操作數據,只是轉換數據 使用map
                .flatMap(user -> this.repository.delete(user)
                        .then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK))))
                .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
    }

    /**
     * 修改用戶信息
     *
     * @param user
     * @return
     */
    @PutMapping("/{id}")
    public Mono<ResponseEntity<User>> updateUser(
            @PathVariable String id,
            @RequestBody User user) {
        return this.repository.findById(id)
                .flatMap(u -> {
                    u.setAge(user.getAge());
                    u.setName(user.getName());
                    return this.repository.save(u);
                })
                .map(u -> new ResponseEntity<>(u, HttpStatus.OK))
                .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
    }

    /**
     * 根據ID查找用戶信息
     *
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public Mono<ResponseEntity<User>> queryById(
            @PathVariable String id) {
        return this.repository.findById(id)
                .map(u -> new ResponseEntity<>(u, HttpStatus.OK))
                .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
    }
}

三、基於RouterFunction的模式

@Component
public class UserHandler {
    private final UserRepository repository;

    public UserHandler(UserRepository repository) {
        this.repository = repository;
    }

    /**
     * 獲取所有用戶信息
     *
     * @param request
     * @return
     */
    public Mono<ServerResponse> getAllUser(ServerRequest request) {
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8)
                .body(this.repository.findAll(), User.class);
    }

    /**
     * 新增用戶信息
     *
     * @param request
     * @return
     */
    public Mono<ServerResponse> createUser(ServerRequest request) {
        Mono<User> user = request.bodyToMono(User.class);
        return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON_UTF8)
                .body(this.repository.saveAll(user), User.class);
    }

    /**
     * 根據ID刪除用戶信息
     *
     * @param request
     * @return
     */
    public Mono<ServerResponse> deleteUserById(ServerRequest request) {
        String id = request.pathVariable("id");
        return this.repository.findById(id).flatMap(
                user -> this.repository.delete(user).then(ServerResponse.ok().build())
                        .switchIfEmpty(ServerResponse.notFound().build()));
    }
}
@Configuration
public class AllRouters {
    @Bean
    RouterFunction<ServerResponse> userRouter(UserHandler handler) {
        return RouterFunctions.nest(
                //相當於Controller類上面的@RequestMapping("/api/user")
                RequestPredicates.path("/api/user")
                //獲取所有用戶信息
                , RouterFunctions.route(RequestPredicates.GET(""), handler::getAllUser)
                        //新增用戶信息
                        .andRoute(RequestPredicates.POST("").and(RequestPredicates.accept(MediaType.APPLICATION_JSON_UTF8)), handler::createUser)
                        //根據ID刪除用戶信息
                        .andRoute(RequestPredicates.DELETE("/{id}"), handler::deleteUserById));
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章