实战基于Spring Boot 2的WebFlux和mLab搭建反应式Web
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包含两个重要的成员变量Flux和Mono,它们都实现了Reactive Streams提供的
Publisher接口
. Flux
是一个代表了0..N元素的流,Mono
是代表了一个0..1元素的流。虽然WebFlux使用Reactor作为它的核心依赖,它在应用层面,它也同时支持RxJava。
Spring WebFlux支持两种类型的编程范式:
传统的基于注解的方式,如@Controller、@RequestMapping等沿用了Spring MVC的模式.
基于Java8的Lambda函数式编程模式
本文主要是使用基于注解的方式,今后另文补充基于函数式编程的范式。
基于Spring Boot 2+ Spring WebFlux + MongoDB的轻量级微博应用
以下展示如何搭建一个轻量级的微博应用,这个应用只包括一个domain类Tweet,使用基于MongoDB的在线MongoDB数据库mLab作为存储,并且使用异步的RESTful API提供基本的增删查改功能。
此外还会用到Spring Test组件,通过使用Maven的插件功能,实现对微服务应用的测试。
1. 新建项目
- 点击http://start.spring.io
- 选择2.x以上的Spring Boot版本
- 输入artifact的值,比如webflux-demo
- 选择Reactive Web和Reactive MongoDB依赖
- 点击Generate Project,生成并下载一个微服务框架到本地,并解压
- 使用IDE,比如eclipse,导入解压出来的项目文件
2. 注册mLab账户,并新建一个MongoDB数据库
MongoDB数据库是常用的文档类型数据库,广泛用于社交网站、电商等引用中。而mLab是一个在线MongoDB数据库平台,提供MongoDB的在线服务。这个应用使用到它。
- 前往https://mlab.com
- 根据要求注册账户
- 网站会有免费和收费的服务选择,选择AWS的免费MongoDB服务
- 服务选择完毕,平台会提供一个数据库镜像,可以点击数据库前往管理页面。
- 在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。@NotBlank、@NotNull和@Size限定了成员变量的值的范围。代码如下:
@Document(collection = "tweets")
public class Tweet {
@Id
private String id; @NotBlank
@Size(max = 140)
private String text; @NotNull
private Date createAt = new Date(); public Tweet() { }
//省略Tweet的getter和setter方法
}
4.2 Repository
Repository接口是DAO,继承了ReactiveMongoRepository接口用于连接MongoDB数据库做数据持久化,
package com.example.webfluxdemo.repository; import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.stereotype.Repository; import com.example.webfluxdemo.model.Tweet; @Repository
public interface TweetRepository extends ReactiveMongoRepository<Tweet, String> { }
其中父接口ReactiveMongoRepository的源码如下:
public interface ReactiveMongoRepository<T, ID> extends ReactiveSortingRepository<T, ID>, ReactiveQueryByExampleExecutor<T> { <S extends T> Mono<S> insert(S entity); <S extends T> Flux<S> insert(Iterable<S> entities); <S extends T> Flux<S> insert(Publisher<S> entities); <S extends T> Flux<S> findAll(Example<S> example); <S extends T> Flux<S> findAll(Example<S> example, Sort sort); }
通过查看源码可知,父接口ReactiveMongoRepository包含对MongoDB数据库基本的增删改查方法。在运行时,Spring Boot会自动实现一个SimpleReactiveMongoRepository类,用于执行增删改查方法。这样极大地节省了程序员持久化的精力,可以专注于业务开发。
4.3 Controller
Controller是WebFlux的核心类,该类定义了增删查改对应的方法,代码如下:
@RestController
public class TweetController { @Autowired
private TweetRepository tweetRepository;
//通过接受Get请求,返回Flux类型的Tweet对象流
@GetMapping("/tweets")
public Flux<Tweet> getAllTweets(){
return tweetRepository.findAll();
}
//通过接受POST请求,新增一个Tweet对象
@PostMapping("/tweets")
public Mono<Tweet> createTweets(@Valid @RequestBody Tweet tweet){
return tweetRepository.save(tweet);
}
//通过id查找Tweet
@GetMapping("/tweets/{id}")
public Mono<ResponseEntity<Tweet>> getTweetById(@PathVariable(value = "id") String tweetId) {
return tweetRepository.findById(tweetId)
.map(savedTweet -> ResponseEntity.ok(savedTweet))
.defaultIfEmpty(ResponseEntity.notFound().build());
}
//通过id更新Tweet,使用到SpringMVC的相关注解
@PutMapping("/tweets/{id}")
public Mono<ResponseEntity<Tweet>> updateTweet(@PathVariable(value = "id") String tweetId,
@Valid @RequestBody Tweet tweet) {
return tweetRepository.findById(tweetId)
.flatMap(existingTweet -> {
existingTweet.setText(tweet.getText());
return tweetRepository.save(existingTweet);
})
.map(updatedTweet -> new ResponseEntity<>(updatedTweet, HttpStatus.OK))
.defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
//通过id删除tweet
@DeleteMapping("/tweets/{id}")
public Mono<ResponseEntity<Void>> deleteTweet(@PathVariable(value = "id") String tweetId) { return tweetRepository.findById(tweetId)
.flatMap(existingTweet ->
tweetRepository.delete(existingTweet)
.then(Mono.just(new ResponseEntity<Void>(HttpStatus.OK)))
)
.defaultIfEmpty(new ResponseEntity<>(HttpStatus.NOT_FOUND));
} // 基于反应式流发送微博至客户端
@GetMapping(value = "/stream/tweets", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Tweet> streamAllTweets() {
return tweetRepository.findAll();
}
}
Controller是FluxWeb编程的核心,与SpringMVC不同,所有的处理方法返回的都是Flux或Mono对象。
Flux 和 Mono 是 Reactor 中的两个基本概念。Flux 表示的是包含 0 到 N 个元素的异步序列。在该序列中可以包含三种不同类型的消息通知:正常的包含元素的消息、序列结束的消息和序列出错的消息。当消息通知产生时,订阅者中对应的方法 onNext(), onComplete()和 onError()会被调用。
Mono 表示的是包含 0 或者 1 个元素的异步序列。该序列中同样可以包含与 Flux 相同的三种类型的消息通知。
Flux 和 Mono 之间可以进行转换。对一个 Flux 序列进行计数操作,得到的结果是一个 Mono<Long>对象。把两个 Mono 序列合并在一起,得到的是一个 Flux 对象。
Controller使用Flux或Mono作为对象,返回给不同的请求。反应式编码主要在最后一个方法:
// 基于反应式流发送微博至客户端
@GetMapping(value = "/stream/tweets", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Tweet> streamAllTweets() {
return tweetRepository.findAll();
}
这个方法和getAllTweet方法一样,会返回一个JSON流到客户端,区别在于streamAllTweets以Server-send-event的方式返回一个Json流到浏览器,这种流可以被浏览器识别和使用。这里涉及到服务器推送事件(Server-Send Event)
服务器推送事件(Server-Sent Events,SSE)允许服务器端不断地推送数据到客户端。相对于 WebSocket 而言,服务器推送事件只支持服务器端到客户端的单向数据传递。虽然功能较弱,但优势在于 SSE 在已有的 HTTP 协议上使用简单易懂的文本格式(如JSON)来表示传输的数据。
作为 W3C 的推荐规范,SSE 在浏览器端的支持也比较广泛,除了 IE 之外的其他浏览器都提供了支持。在 IE 上也可以使用 polyfill 库来提供支持。在服务器端来说,SSE 是一个不断产生新数据的流,非常适合于用反应式流来表示。在 WebFlux 中创建 SSE 的服务器端是非常简单的。只需要返回的对象的类型是 Flux<ServerSentEvent>,就会被自动按照 SSE 规范要求的格式来发送响应。
使用WebTestClient测试应用
WebTestClient是Spring 5提供的一个异步反应式Http客户端,可以用于测试反应式的RestFul微服务应用。在IDE的测试文件夹中,可以找到测试类,编写代码如下:
public class WebfluxDemoApplicationTests { @Autowired
private WebTestClient webTestClient; @Autowired
TweetRepository tweetRepository; @Test
public void testCreateTweet() {
Tweet tweet = new Tweet("这是一条测试微博"); webTestClient.post().uri("/tweets")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.body(Mono.just(tweet), Tweet.class)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
.expectBody()
.jsonPath("$.id").isNotEmpty()
.jsonPath("$.text").isEqualTo("这是一条测试微博");
}
}
在测试类中通过控制反转注入WebTestClient和DAO的对象,调用WebTestClient方法进行测试,使用mvn test命令,测试所有的测试类。结果如下:
查看mLab的数据库,数据被成功添加:
实战基于Spring Boot 2的WebFlux和mLab搭建反应式Web的更多相关文章
- 基于Spring Boot/Spring Session/Redis的分布式Session共享解决方案
分布式Web网站一般都会碰到集群session共享问题,之前也做过一些Spring3的项目,当时解决这个问题做过两种方案,一是利用nginx,session交给nginx控制,但是这个需要额外工作较多 ...
- step6----->往工程中添加spring boot项目------->修改pom.xml使得我的project是基于spring boot的,而非直接基于spring framework
文章内容概述: spring项目组其实有多个projects,如spring IO platform用于管理external dependencies的版本,通过定义BOM(bill of mater ...
- 基于Spring Boot的图片上传
package com.clou.inteface.domain.web.user; import java.io.File; import java.io.IOException; import j ...
- Https系列之三:让服务器同时支持http、https,基于spring boot
Https系列会在下面几篇文章中分别作介绍: 一:https的简单介绍及SSL证书的生成二:https的SSL证书在服务器端的部署,基于tomcat,spring boot三:让服务器同时支持http ...
- 基于Spring Boot,使用JPA动态调用Sql查询数据
在<基于Spring Boot,使用JPA操作Sql Server数据库完成CRUD>,<基于Spring Boot,使用JPA调用Sql Server数据库的存储过程并返回记录集合 ...
- 基于Spring Boot,使用JPA调用Sql Server数据库的存储过程并返回记录集合
在上一篇<基于Spring Boot,使用JPA操作Sql Server数据库完成CRUD>中完成了使用JPA对实体数据的CRUD操作. 那么,有些情况,会把一些查询语句写在存储过程中,由 ...
- 基于Spring Boot、Spring Cloud、Docker的微服务系统架构实践
由于最近公司业务需要,需要搭建基于Spring Cloud的微服务系统.遍访各大搜索引擎,发现国内资料少之又少,也难怪,国内Dubbo正统治着天下.但是,一个技术总有它的瓶颈,Dubbo也有它捉襟见肘 ...
- 基于Spring Boot和Shiro的后台管理系统FEBS
FEBS是一个简单高效的后台权限管理系统.项目基础框架采用全新的Java Web开发框架 —— Spring Boot 2.0.3,消除了繁杂的XML配置,使得二次开发更为简单:数据访问层采用Myba ...
- Cola Cloud 基于 Spring Boot, Spring Cloud 构建微服务架构企业级开发平台
Cola Cloud 基于 Spring Boot, Spring Cloud 构建微服务架构企业级开发平台: https://gitee.com/leecho/cola-cloud
随机推荐
- PAT B1022 D进制的A+B (20 分)
输入两个非负 10 进制整数 A 和 B (≤),输出 A+B 的 D (1)进制数. 输入格式: 输入在一行中依次给出 3 个整数 A.B 和 D. 输出格式: 输出 A+B 的 D 进制数. 输入 ...
- 贪心算法——字典序最小问题,Saruman‘s Army
题目描述 Best Cow Line (POJ 3617) 给定长度为N的字符串S,要构造一个长度为N字符串T.T是一个空串,反复执行下列任意操作: 从S的头部删除一个字符,加到T的尾部: 从S的尾部 ...
- day78
昨日回顾: forms组件: -校验数据(最重要) -先定义一个类(继承Form) -写一些要校验的字段(好多类型) -字段(对象)有一些属性(最长多少,最短多少,是否必填,l ...
- C#数组、js数组、json
C#数组 参考地址C#之数组 什么是数组?数组是一种数据结构,包含同一个类型的多个元素.数组的声明:int[] myIntArray; 注:声明数组时,方括号 [] 必须跟在类型后面,而不是变量名后面 ...
- 开关电源PCB设计中的布线技巧
开关电源PCB设计中的布线技巧关键字:布线 开关电源 走线 一.引言 开关电源是一种电压转换电路,主要的工作内容是升压和降压,广泛应用于现代电子产品.因为开关三极管总是工作在 “开” 和“关” 的状态 ...
- SAP函数 LAST_DAY_OF_MONTHS 获取月末最后一天日期
DATA LAST_DATE TYPE SY-DATUM. CALL FUNCTION 'LAST_DAY_OF_MONTHS' EXPORTING day_in = sy-datum IMPORTI ...
- python 回溯法 子集树模板 系列 —— 14、最长公共子序列(LCS)
问题 输入 第1行:字符串A 第2行:字符串B (A,B的长度 <= 1000) 输出 输出最长的子序列,如果有多个,随意输出1个. 输入示例 belong cnblogs 输出示例 blog ...
- 对JSON传递图片Base64编码的一点总结
项目中跟Java对接的时候需要传输图片,经过Base64编码后传输的. 但是实际调试的时候发现Java那边始终无法正常解析出图片. 冷静想想之后,发现问题在于使用OpenCV读取图片,编码的是Mat: ...
- selenium常用命令
openopen(url)- 在浏览器中打开URL,可以接受相对和绝对路径两种形式type type(inputLocator, value)- 模拟人手的输入过程,往指定的input中输入值- 也适 ...
- REST-framework快速构建API--权限
我们在访问资源时,有些资源保密程度较高,需要特殊的人员才能访问.比如,获取公司的每日收入流水的API接口,只能CEO才能查看. 这时,我们就需要将资源设定权限了. REST-framework实现如下 ...