热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

实战基于SpringBoot2的WebFlux和mLab搭建反应式Web

SpringFramework5带来了新的ReactiveStack非阻塞式Web框架:SpringWebFlux。作为与SpringMVC并行使用的Web框架,SpringWeb

Spring Framework 5带来了新的Reactive Stack非阻塞式Web框架:Spring WebFlux。作为与Spring MVC并行使用的Web框架,Spring WebFlux依赖了反应式流适配器(Reactive Streams Adapter),在Netty和Servlet3.1的容器下,可以提供非阻塞式的Web服务,充分发挥下一代多核处理器的优势,支撑海量的并发访问。

以上是官网的介绍,事实上在基于Spring Boot 2强大的微服务架构帮助下,WebFlux和Spring MVC一起,成为Java应用开发的两大选择,可以让我们迅速地搭建起反应式的Web应用。本文拟通过模拟一个简单的微博应用,实战通过Spring Boot 2+ Spring WebFlux + MongoDB 开发一个Web应用。

Spring WebFlux及其编程范式

Spring WebFlux通过核心库Reactor提供反应式支持,Reactor实现了Reactive Streams,后者是一个带非阻塞式背压的异步流处理器。

Reactor包含两个重要的成员变量FluxMono,它们都实现了Reactive Streams提供的Publisher接口Flux 是一个代表了0..N元素的流,Mono是代表了一个0..1元素的流。虽然WebFlux使用Reactor作为它的核心依赖,它在应用层面,它也同时支持RxJava。

Spring WebFlux支持两种类型的编程范式:

  1. 传统的基于注解的方式,如@Controller、@RequestMapping等沿用了Spring MVC的模式.

  2. 基于Java8的Lambda函数式编程模式

本文主要是使用基于注解的方式,今后另文补充基于函数式编程的范式。

基于Spring Boot 2+ Spring WebFlux + MongoDB的轻量级微博应用

以下展示如何搭建一个轻量级的微博应用,这个应用只包括一个domain类Tweet,使用基于MongoDB的在线MongoDB数据库mLab作为存储,并且使用异步的RESTful API提供基本的增删查改功能。

此外还会用到Spring Test组件,通过使用Maven的插件功能,实现对微服务应用的测试。

1. 新建项目

  1. 点击http://start.spring.io
  2. 选择2.x以上的Spring Boot版本
  3. 输入artifact的值,比如webflux-demo
  4. 选择Reactive Web和Reactive MongoDB依赖
  5. 点击Generate Project,生成并下载一个微服务框架到本地,并解压
  6. 使用IDE,比如eclipse,导入解压出来的项目文件

2. 注册mLab账户,并新建一个MongoDB数据库

MongoDB数据库是常用的文档类型数据库,广泛用于社交网站、电商等引用中。而mLab是一个在线MongoDB数据库平台,提供MongoDB的在线服务。这个应用使用到它。

  1. 前往https://mlab.com
  2. 根据要求注册账户
  3. 网站会有免费和收费的服务选择,选择AWS的免费MongoDB服务
  4. 服务选择完毕,平台会提供一个数据库镜像,可以点击数据库前往管理页面。
  5. 在User标签下,新建数据库的登录名和密码。

完成以上步骤,数据库就可以开始使用了。你会看到如下图所示的页面:

3. 在项目中配置MongoDB数据库

前往IDE中的项目资源文件夹,找到application.properties。添加你在mLad的MongoDB URI

spring.data.mongodb.uri=mongodb://username:password@ds063439.mlab.com:63439/springdb   

在应用启动的时候,Springboot会自动读取该配置文件。

4. 编写应用各模块

WebFlux可以认为是基于Spring的Web开发的一个新的模式或选择,因此它既有Spring MVC有的模块如Domain、Controller、Service,也有新增的如Handler、Router等。下面分别编写各模块。

4.1 Domain包

Domain包只包括一个domain类Tweet.java,因为使用了文档数据库,因此使用@Document注解修饰类,并且使用@Id修饰成员变量id。代码如下:

 1 package com.example.webfluxdemo.model;
 2 
 3 import java.util.Date;
 4 
 5 import javax.validation.constraints.NotBlank;
 6 import javax.validation.constraints.NotNull;
 7 import javax.validation.constraints.Size;
 8 
 9 import org.springframework.data.annotation.Id;
10 import org.springframework.data.mongodb.core.mapping.Document;
11 
12 @Document(collection = "tweets")
13 public class Tweet {
14     @Id
15     private String id;
16     
17     @NotBlank
18     @Size(max = 140)
19     private String text;
20     
21     @NotNull
22     private Date createAt = new Date();
23     
24     public Tweet() {
25         
26     }
27 
28     public Tweet(String text) {
29         this.text = text;
30     }
31 
32     public String getId() {
33         return id;
34     }
35 
36     public void setId(String id) {
37         this.id = id;
38     }
39 
40     public String getText() {
41         return text;
42     }
43 
44     public void setText(String text) {
45         this.text = text;
46     }
47 
48     public Date getCreateAt() {
49         return createAt;
50     }
51 
52     public void setCreateAt(Date createAt) {
53         this.createAt = createAt;
54     }
55     
56 }

4.2 Repository

Repository接口是DAO,继承了ReactiveMongoRepository接口用于连接MongoDB数据库做数据持久化,

 1 package com.example.webfluxdemo.repository;
 2 
 3 import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
 4 import org.springframework.stereotype.Repository;
 5 
 6 import com.example.webfluxdemo.model.Tweet;
 7 
 8 @Repository
 9 public interface TweetRepository extends ReactiveMongoRepository {
10 
11 }

通过查看源码可知,父接口ReactiveMongoRepository包含对MongoDB数据库基本的增删改查方法。在运行时,Spring Boot会自动实现一个SimpleReactiveMongoRepository类,用于执行增删改查方法。这样极大地节省了程序员持久化的精力,可以专注于业务开发。

4.3 Controller

Controller是WebFlux的核心类,代码如下:

 1 package com.example.webfluxdemo.controller;
 2 
 3 import javax.validation.Valid;
 4 
 5 import org.springframework.beans.factory.annotation.Autowired;
 6 import org.springframework.http.HttpStatus;
 7 import org.springframework.http.MediaType;
 8 import org.springframework.http.ResponseEntity;
 9 import org.springframework.web.bind.annotation.DeleteMapping;
10 import org.springframework.web.bind.annotation.GetMapping;
11 import org.springframework.web.bind.annotation.PathVariable;
12 import org.springframework.web.bind.annotation.PostMapping;
13 import org.springframework.web.bind.annotation.PutMapping;
14 import org.springframework.web.bind.annotation.RequestBody;
15 import org.springframework.web.bind.annotation.RestController;
16 
17 import com.example.webfluxdemo.model.Tweet;
18 import com.example.webfluxdemo.repository.TweetRepository;
19 
20 import reactor.core.publisher.Flux;
21 import reactor.core.publisher.Mono;
22 
23 @RestController
24 public class TweetController {
25     
26     @Autowired
27     private TweetRepository tweetRepository;
28     
29     @GetMapping("/tweets")
30     public Flux getAllTweets(){
31         return tweetRepository.findAll();
32     }
33     
34     @PostMapping("/tweets")
35     public Mono createTweets(@Valid @RequestBody Tweet tweet){
36         return tweetRepository.save(tweet);
37     }
38     
39     @GetMapping("/tweets/{id}")
40     public Mono> getTweetById(@PathVariable(value = "id") String tweetId) {
41         return tweetRepository.findById(tweetId)
42                 .map(savedTweet -> ResponseEntity.ok(savedTweet))
43                 .defaultIfEmpty(ResponseEntity.notFound().build());
44     }
45 
46     @PutMapping("/tweets/{id}")
47     public Mono> updateTweet(@PathVariable(value = "id") String tweetId,
48                                                    @Valid @RequestBody Tweet tweet) {
49         return tweetRepository.findById(tweetId)
50                 .flatMap(existingTweet -> {
51                     existingTweet.setText(tweet.getText());
52                     return tweetRepository.save(existingTweet);
53                 })
54                 .map(updatedTweet -> new ResponseEntity<>(updatedTweet, HttpStatus.OK))
55                 .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
56     }
57 
58     @DeleteMapping("/tweets/{id}")
59     public Mono> deleteTweet(@PathVariable(value = "id") String tweetId) {
60 
61         return tweetRepository.findById(tweetId)
62                 .flatMap(existingTweet ->
63                         tweetRepository.delete(existingTweet)
64                             .then(Mono.just(new ResponseEntity(HttpStatus.OK)))
65                 )
66                 .defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
67     }
68 
69     // 基于反应式流发送微博至客户端
70     @GetMapping(value = "/stream/tweets", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
71     public Flux streamAllTweets() {
72         return tweetRepository.findAll();
73     }
74 
75 }

Controller使用Flux或Mono作为对象,返回给不同的请求。反应式编码主要在最后一个方法:

// 基于反应式流发送微博至客户端
    @GetMapping(value = "/stream/tweets", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux streamAllTweets() {
        return tweetRepository.findAll();
    }  

这个方法和getAllTweet方法一样,会返回一个JSON流到客户端,区别在于streamAllTweets以Server-send-event的方式返回一个Json流到浏览器,这种流可以被浏览器识别和使用。

使用WebTestClient测试应用

WebTestClient是Spring 5提供的一个异步反应式Http客户端,可以用于测试反应式的RestFul微服务应用。在IDE的测试文件夹中,可以找到测试类,编写代码如下:

 1 package com.example.webfluxdemo;
 2 
 3 import java.util.Collections;
 4 
 5 import org.assertj.core.api.Assertions;
 6 import org.junit.Test;
 7 import org.junit.runner.RunWith;
 8 import org.springframework.beans.factory.annotation.Autowired;
 9 import org.springframework.boot.test.context.SpringBootTest;
10 import org.springframework.http.MediaType;
11 import org.springframework.test.context.junit4.SpringRunner;
12 import org.springframework.test.web.reactive.server.WebTestClient;
13 
14 import com.example.webfluxdemo.model.Tweet;
15 import com.example.webfluxdemo.repository.TweetRepository;
16 
17 import reactor.core.publisher.Mono;
18 
19 @RunWith(SpringRunner.class)
20 @SpringBootTest(webEnvirOnment= SpringBootTest.WebEnvironment.RANDOM_PORT)
21 public class WebfluxDemoApplicationTests {
22 
23     @Autowired
24     private WebTestClient webTestClient;
25     
26     @Autowired
27     TweetRepository tweetRepository;
28     
29     @Test
30     public void testCreateTweet() {
31         Tweet tweet = new Tweet("这是一条测试微博");
32 
33         webTestClient.post().uri("/tweets")
34                 .contentType(MediaType.APPLICATION_JSON_UTF8)
35                 .accept(MediaType.APPLICATION_JSON_UTF8)
36                 .body(Mono.just(tweet), Tweet.class)
37                 .exchange()
38                 .expectStatus().isOk()
39                 .expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
40                 .expectBody()
41                 .jsonPath("$.id").isNotEmpty()
42                 .jsonPath("$.text").isEqualTo("这是一条测试微博");
43     }
44     
45     @Test
46     public void testGetAllTweets() {
47         webTestClient.get().uri("/tweets")
48                 .accept(MediaType.APPLICATION_JSON_UTF8)
49                 .exchange()
50                 .expectStatus().isOk()
51                 .expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
52                 .expectBodyList(Tweet.class);
53     }
54 
55     @Test
56     public void testGetSingleTweet() {
57         Tweet tweet = tweetRepository.save(new Tweet("Hello, World!")).block();
58 
59         webTestClient.get()
60                 .uri("/tweets/{id}", Collections.singletonMap("id", tweet.getId()))
61                 .exchange()
62                 .expectStatus().isOk()
63                 .expectBody()
64                 .consumeWith(response ->
65                         Assertions.assertThat(response.getResponseBody()).isNotNull());
66     }
67 
68     @Test
69     public void testUpdateTweet() {
70         Tweet tweet = tweetRepository.save(new Tweet("Initial Tweet")).block();
71 
72         Tweet newTweetData = new Tweet("更新微博");
73 
74         webTestClient.put()
75                 .uri("/tweets/{id}", Collections.singletonMap("id", tweet.getId()))
76                 .contentType(MediaType.APPLICATION_JSON_UTF8)
77                 .accept(MediaType.APPLICATION_JSON_UTF8)
78                 .body(Mono.just(newTweetData), Tweet.class)
79                 .exchange()
80                 .expectStatus().isOk()
81                 .expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
82                 .expectBody()
83                 .jsonPath("$.text").isEqualTo("更新微博");
84     }
85 
86     @Test
87     public void testDeleteTweet() {
88         Tweet tweet = tweetRepository.save(new Tweet("将要被删除的微博")).block();
89 
90         webTestClient.delete()
91                 .uri("/tweets/{id}", Collections.singletonMap("id",  tweet.getId()))
92                 .exchange()
93                 .expectStatus().isOk();
94     }
95 
96 }

使用mvn test命令,测试所有的测试类。结果如下:

查看mLab的数据库,数据被成功添加:

 

 

 

 

 

 

posted on 2018-11-13 14:20 Leoliu168 阅读(...) 评论(...) 编辑 收藏

推荐阅读
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • 2021年Java开发实战:当前时间戳转换方法详解与实用网址推荐
    在当前的就业市场中,金九银十过后,金三银四也即将到来。本文将分享一些实用的面试技巧和题目,特别是针对正在寻找新工作机会的Java开发者。作者在准备字节跳动的面试过程中积累了丰富的经验,并成功获得了Offer。文中详细介绍了如何将当前时间戳进行转换的方法,并推荐了一些实用的在线资源,帮助读者更好地应对技术面试。 ... [详细]
  • 阿里巴巴Java后端开发面试:TCP、Netty、HashMap、并发锁与红黑树深度解析 ... [详细]
  • 通过一张截图深入解析字节跳动的 Java 开发实力
    在与一位来自字节跳动的朋友交流时了解到,根据他们近期招聘Java工程师的经验,大多数候选人往往在工作3年后会遇到一个难以跨越的瓶颈期。这是因为在职业生涯的这个阶段,许多工程师的技术深度和广度已经达到了一定的水平,但要进一步提升则需要更多的挑战和学习机会。字节跳动作为一家技术驱动的公司,通过严格的面试流程和实际项目经验,能够更好地评估候选人的技术水平和发展潜力。 ... [详细]
  • 字节Java高级岗:java开发cpu吃多线程吗
    前言抱着侥幸心理投了字节跳动后台JAVA开发岗,居然收到通知去面试,一面下整个人来都是懵逼的,不知道我对着面试官都说了些啥(捂脸~~)。侥幸一面居然过了,三天后接到二面通知,结果这 ... [详细]
  • spring cloud微服务实战 pdf_springcloud微服务架构开发实战:常见微服务的消费者
    常见微服务的消费者本节就常见的微服务的消费者进行介绍。在Java领域比较常用的消费者框架主要有HttpClient、Ribbon、Feign等。ApacheHttpClientAp ... [详细]
  • 秒建一个后台管理系统?用这5个开源免费的Java项目就够了
    秒建一个后台管理系统?用这5个开源免费的Java项目就够了 ... [详细]
  • 阿里巴巴终面技术挑战:如何利用 UDP 实现 TCP 功能?
    在阿里巴巴的技术面试中,技术总监曾提出一道关于如何利用 UDP 实现 TCP 功能的问题。当时回答得不够理想,因此事后进行了详细总结。通过与总监的进一步交流,了解到这是一道常见的阿里面试题。面试官的主要目的是考察应聘者对 UDP 和 TCP 在原理上的差异的理解,以及如何通过 UDP 实现类似 TCP 的可靠传输机制。 ... [详细]
  • 本文详细探讨了几种常用的Java后端开发框架组合及其具体应用场景。通过对比分析Spring Boot、MyBatis、Hibernate等框架的特点和优势,结合实际项目需求,为开发者提供了选择合适框架组合的参考依据。同时,文章还介绍了这些框架在微服务架构中的应用,帮助读者更好地理解和运用这些技术。 ... [详细]
  • 深入解析Netty:基础理论与IO模型概述
    深入解析Netty:基础理论与IO模型概述 ... [详细]
  • 当前,众多初创企业对全栈工程师的需求日益增长,但市场中却存在大量所谓的“伪全栈工程师”,尤其是那些仅掌握了Node.js技能的前端开发人员。本文旨在深入探讨全栈工程师在现代技术生态中的真实角色与价值,澄清对这一角色的误解,并强调真正的全栈工程师应具备全面的技术栈和综合解决问题的能力。 ... [详细]
  • JVM上高性能数据格式库包Apache Arrow入门和架构的示例分析
    小编给大家分享一下JVM上高性能数据格式库包ApacheArrow入门和架构的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!Apac ... [详细]
  • eureka out of service(eureka服务下线感知)
    近日,知名服务注册与服务发现工具Eureka的GitHubWiki上显示其2.0版本的开源工作已经停止。关于第二篇,我想说,这是在造谣。看起来挺吓人的。关于Eureka ... [详细]
  • 本教程详细介绍了如何使用 Spring Boot 创建一个简单的 Hello World 应用程序。适合初学者快速上手。 ... [详细]
  • 提升 Kubernetes 集群管理效率的七大专业工具
    Kubernetes 在云原生环境中的应用日益广泛,然而集群管理的复杂性也随之增加。为了提高管理效率,本文推荐了七款专业工具,这些工具不仅能够简化日常操作,还能提升系统的稳定性和安全性。从自动化部署到监控和故障排查,这些工具覆盖了集群管理的各个方面,帮助管理员更好地应对挑战。 ... [详细]
author-avatar
越野瘾君子_939
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有