springboot 使用webflux响应式开发教程(二)
本篇是对springboot 使用webflux响应式开发教程(一)的进一步学习。
分三个部分:
数据库操作
webservice
websocket
创建项目,artifactId = trading-service,groupId=io.spring.workshop。选择Reactive Web , Devtools, Thymeleaf , Reactive Mongo。
WEB容器
spring-boot-starter-webflux 附带了 spring-boot-starter-reactor-netty,所以默认使用Reactor Netty作为web server。
如果要用Tomcat,添加pom即可
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-tomcat</artifactId>
- </dependency>
- 同样支持Undertow和Jetty
响应式数据库操作
这个示例使用MongoDB。作为reactive模式,数据库的驱动与传统模式区分开。截至目前还没有mysql的reactive驱动,据悉正在研发。本例中使用内存版的mongodb,需要添加依赖
- <dependency>
- <groupId>de.flapdoodle.embed</groupId>
- <artifactId>de.flapdoodle.embed.mongo</artifactId>
- </dependency>
- 在初次运行时会自动下载mongodb模块,但是墙国是直连不到mongodb的官网,所以在需要添加代理。在这推荐使用JVM参数的方式,-DproxySet=true -Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=。需要注意的是http和https协议是区分开来配置的,如果需要http的代理就需要把Dhttps改为Dhttp。
- 数据库的存储实体 TradingUser
- @Document
- @Data
- public class TradingUser {
- @Id
- private String id;
- private String userName;
- private String fullName;
- public TradingUser() {
- }
- public TradingUser(String id, String userName, String fullName) {
- this.id = id;
- this.userName = userName;
- this.fullName = fullName;
- }
- public TradingUser(String userName, String fullName) {
- this.userName = userName;
- this.fullName = fullName;
- }
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- TradingUser that = (TradingUser) o;
- if (!id.equals(that.id)) return false;
- return userName.equals(that.userName);
- }
- @Override
- public int hashCode() {
- int result = id.hashCode();
- result = * result + userName.hashCode();
- return result;
- }
- }
创建TradingUserRepository继承ReactiveMongoRepository。添加findByUserName方法返回一个实体。
在项目启动的时候我们要初始化一些数据,为此创建UsersCommandLineRunner并继承CommandLineRunner并重写run方法,在该方法里初始化数据,并插入到数据库中。
- @Component
- public class UsersCommandLineRunner implements CommandLineRunner {
- private final TradingUserRepository repository;
- public UsersCommandLineRunner(TradingUserRepository repository) {
- this.repository = repository;
- }
- @Override
- public void run(String... strings) throws Exception {
- List<TradingUser> users = Arrays.asList(
- new TradingUser("sdeleuze", "Sebastien Deleuze"),
- new TradingUser("snicoll", "Stephane Nicoll"),
- new TradingUser("rstoyanchev", "Rossen Stoyanchev"),
- new TradingUser("poutsma", "Arjen Poutsma"),
- new TradingUser("smaldini", "Stephane Maldini"),
- new TradingUser("simonbasle", "Simon Basle"),
- new TradingUser("violetagg", "Violeta Georgieva"),
- new TradingUser("bclozel", "Brian Clozel")
- );
- this.repository.insert(users).blockLast(Duration.ofSeconds());
- }
- }
- 由于该方法是void类型,实现是阻塞的,因此在 repository 插入数据返回Flux的时候需要调用 blockLast(Duration)
- 。也可以使用 then().block(Duration) 将 Flux 转化为 Mono<Void> 等待执行结束。
创建 webservice, @RestController标注 的 UserController,添加两个控制器方法
1、get请求,”/users”,返回所有TradingUser,content-type = “application/json”
2、get请求,”/users/{username}”,返回单个TradingUser,content-type = “application/json”
- @RestController
- public class UserController {
- private final TradingUserRepository tradingUserRepository;
- public UserController(TradingUserRepository tradingUserRepository) {
- this.tradingUserRepository = tradingUserRepository;
- }
- @GetMapping(path = "/users", produces = MediaType.APPLICATION_JSON_VALUE)
- public Flux<TradingUser> listUsers() {
- return this.tradingUserRepository.findAll();
- }
- @GetMapping(path = "/users/{username}", produces = MediaType.APPLICATION_JSON_VALUE)
- public Mono<TradingUser> showUsers(@PathVariable String username) {
- return this.tradingUserRepository.findByUserName(username);
- }
- }
编写测试
- @RunWith(SpringRunner.class)
- @WebFluxTest(UserController.class)
- public class UserControllerTests {
- @Autowired
- private WebTestClient webTestClient;
- @MockBean
- private TradingUserRepository repository;
- @Test
- public void listUsers() {
- TradingUser juergen = new TradingUser("", "jhoeller", "Juergen Hoeller");
- TradingUser andy = new TradingUser("", "wilkinsona", "Andy Wilkinson");
- BDDMockito.given(this.repository.findAll())
- .willReturn(Flux.just(juergen, andy));
- this.webTestClient.get().uri("/users").accept(MediaType.APPLICATION_JSON)
- .exchange()
- .expectBodyList(TradingUser.class)
- .hasSize()
- .contains(juergen, andy);
- }
- @Test
- public void showUser() {
- TradingUser juergen = new TradingUser("", "jhoeller", "Juergen Hoeller");
- BDDMockito.given(this.repository.findByUserName("jhoeller"))
- .willReturn(Mono.just(juergen));
- this.webTestClient.get().uri("/users/jhoeller").accept(MediaType.APPLICATION_JSON)
- .exchange()
- .expectBody(TradingUser.class)
- .isEqualTo(juergen);
- }
- }
用Thymeleaf渲染页面
pom添加前端依赖
- <dependency>
- <groupId>org.webjars</groupId>
- <artifactId>bootstrap</artifactId>
- <version>3.3.</version>
- </dependency>
- <dependency>
- <groupId>org.webjars</groupId>
- <artifactId>highcharts</artifactId>
- <version>5.0.</version>
- </dependency>
创建HomeController
- @Controller
- public class HomeController {
- private final TradingUserRepository tradingUserRepository;
- public HomeController(TradingUserRepository tradingUserRepository) {
- this.tradingUserRepository = tradingUserRepository;
- }
- @GetMapping("/")
- public String home(Model model) {
- model.addAttribute("users", this.tradingUserRepository.findAll());
- return "index";
- }
- }
创建首页
- <!DOCTYPE html>
- <html lang="en" xmlns:th="http://www.thymeleaf.org">
- <head>
- <meta charset="utf-8"/>
- <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
- <meta name="viewport" content="width=device-width, initial-scale=1"/>
- <meta name="description" content="Spring WebFlux Workshop"/>
- <meta name="author" content="Violeta Georgieva and Brian Clozel"/>
- <title>Spring Trading application</title>
- <link rel="stylesheet" href="/webjars/bootstrap/3.3.7/css/bootstrap-theme.min.css"/>
- <link rel="stylesheet" href="/webjars/bootstrap/3.3.7/css/bootstrap.min.css"/>
- </head>
- <body>
- <nav class="navbar navbar-default">
- <div class="container-fluid">
- <div class="navbar-header">
- <a class="navbar-brand" href="/">Spring Trading application</a>
- </div>
- <div id="navbar" class="navbar-collapse collapse">
- <ul class="nav navbar-nav">
- <li class="active"><a href="/">Home</a></li>
- <li><a href="/quotes">Quotes</a></li>
- <li><a href="/websocket">Websocket</a></li>
- </ul>
- </div>
- </div>
- </nav>
- <div class="container wrapper">
- <h2>Trading users</h2>
- <table class="table table-striped">
- <thead>
- <tr>
- <th>#</th>
- <th>User name</th>
- <th>Full name</th>
- </tr>
- </thead>
- <tbody>
- <tr th:each="user: ${users}">
- <th scope="row" th:text="${user.id}"></th>
- <td th:text="${user.userName}">janedoe</td>
- <td th:text="${user.fullName}">Jane Doe</td>
- </tr>
- </tbody>
- </table>
- </div>
- <script type="text/javascript" src="/webjars/jquery/1.11.1/jquery.min.js"></script>
- <script type="text/javascript" src="/webjars/bootstrap/3.3.7/js/bootstrap.min.js"></script>
- </body>
- </html>
- Spring WebFlux在渲染视图之前自动解析Publisher实例,因此不需包含阻塞代码
使用WebClient 将 stream JSON 输送到浏览器
现在要用到springboot 使用webflux响应式开发教程(一)的示例,远程调用该服务。然后创建视图
- <!DOCTYPE html>
- <html lang="en" xmlns:th="http://www.thymeleaf.org">
- <head>
- <meta charset="utf-8"/>
- <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
- <meta name="viewport" content="width=device-width, initial-scale=1"/>
- <meta name="description" content="Spring WebFlux Workshop"/>
- <meta name="author" content="Violeta Georgieva and Brian Clozel"/>
- <title>Spring Trading application</title>
- <link rel="stylesheet" href="/webjars/bootstrap/3.3.7/css/bootstrap-theme.min.css"/>
- <link rel="stylesheet" href="/webjars/bootstrap/3.3.7/css/bootstrap.min.css"/>
- <link rel="stylesheet" href="/webjars/highcharts/5.0.8/css/highcharts.css"/>
- </head>
- <body>
- <nav class="navbar navbar-default">
- <div class="container-fluid">
- <div class="navbar-header">
- <a class="navbar-brand" href="/">Spring Trading application</a>
- </div>
- <div id="navbar" class="navbar-collapse collapse">
- <ul class="nav navbar-nav">
- <li><a href="/">Home</a></li>
- <li class="active"><a href="/quotes">Quotes</a></li>
- <li><a href="/websocket">Websocket</a></li>
- </ul>
- </div>
- </div>
- </nav>
- <div class="container wrapper">
- <div id="chart" style="height: 400px; min-width: 310px"></div>
- </div>
- <script type="text/javascript" src="/webjars/jquery/1.11.1/jquery.min.js"></script>
- <script type="text/javascript" src="/webjars/highcharts/5.0.8/highcharts.js"></script>
- <script type="text/javascript" src="/webjars/bootstrap/3.3.7/js/bootstrap.min.js"></script>
- <script type="text/javascript">
- // Setting up the chart
- var chart = new Highcharts.chart('chart', {
- title: {
- text: 'My Stock Portfolio'
- },
- yAxis: {
- title: {
- text: 'Stock Price'
- }
- },
- legend: {
- layout: 'vertical',
- align: 'right',
- verticalAlign: 'middle'
- },
- xAxis: {
- type: 'datetime',
- },
- series: [{
- name: 'CTXS',
- data: []
- }, {
- name: 'MSFT',
- data: []
- }, {
- name: 'ORCL',
- data: []
- }, {
- name: 'RHT',
- data: []
- }, {
- name: 'VMW',
- data: []
- }, {
- name: 'DELL',
- data: []
- }]
- });
- // This function adds the given data point to the chart
- var appendStockData = function (quote) {
- chart.series
- .filter(function (serie) {
- return serie.name == quote.ticker
- })
- .forEach(function (serie) {
- var shift = serie.data.length > ;
- serie.addPoint([new Date(quote.instant), quote.price], true, shift);
- });
- };
- // The browser connects to the server and receives quotes using ServerSentEvents
- // those quotes are appended to the chart as they're received
- var stockEventSource = new EventSource("/quotes/feed");
- stockEventSource.onmessage = function (e) {
- appendStockData(JSON.parse(e.data));
- };
- </script>
- </body>
- </html>
- 页面会通过Server Sent Event(SSE) 向服务器请求Quotes。
创建控制器QuotesController并添加两个方法如下
- @Controller
- public class QuotesController {
- @GetMapping("/quotes")
- public String quotes() {
- return "quotes";
- }
- @GetMapping(path = "/quotes/feed", produces = TEXT_EVENT_STREAM_VALUE)
- @ResponseBody
- public Flux<Quote> quotesStream() {
- return WebClient.create("http://localhost:8081")
- .get()
- .uri("/quotes")
- .accept(APPLICATION_STREAM_JSON)
- .retrieve()
- .bodyToFlux(Quote.class)
- .share()
- .log("io.spring.workshop.tradingservice");
- }
- }
- quotesStream方法返回的content-type为”text/event-stream”,并将Flux<Quote>作为响应主体,数据已由stock-quotes提供,在这使用WebClient来请求并检索数据。
- 同时应该避免为每个浏览器的请求都去向数据服务提供方发送请求,可以使用Flux.share()
接下来进入页面查看效果
创建WebSocket Handler
WebFlux 支持函数响应式WebSocket 客户端和服务端。
服务端主要分两部分:WebSocketHandlerAdapter 负责处理请求,然后委托给WebSocketService和WebSocketHandler返回响应完成会话。
spring mvc 的 reactive websocket 官方文档参考 这里.
先创建EchoWebSocketHandler
实现 WebSocketHandler
接口
- public class EchoWebSocketHandler implements WebSocketHandler {
- @Override
- public Mono<Void> handle(WebSocketSession session) {
- return session.send(session.receive()
- .doOnNext(WebSocketMessage::retain)
- .delayElements(Duration.ofSeconds()).log());
- }
- }
实现handle
方法,接收传入的消息然后在延迟一秒后输出。
为了将请求映射到Handler
,需要创建WebSocketRouter
,
- @Configuration
- public class WebSocketRouter {
- @Bean
- public HandlerMapping handlerMapping() {
- Map<String, WebSocketHandler> map = new HashMap<>();
- map.put("/websocket/echo", new EchoWebSocketHandler());
- SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
- mapping.setOrder();
- mapping.setUrlMap(map);
- return mapping;
- }
- @Bean
- public WebSocketHandlerAdapter handlerAdapter() {
- return new WebSocketHandlerAdapter();
- }
- }
然后创建WebSocketController
- @Controller
- public class WebSocketController {
- @GetMapping("/websocket")
- public String websocket() {
- return "websocket";
- }
- }
返回视图,在页面上查看效果
- <!DOCTYPE html>
- <html lang="en" xmlns:th="http://www.thymeleaf.org">
- <head>
- <meta charset="utf-8"/>
- <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
- <meta name="viewport" content="width=device-width, initial-scale=1"/>
- <meta name="description" content="Spring WebFlux Workshop"/>
- <meta name="author" content="Violeta Georgieva and Brian Clozel"/>
- <title>Spring Trading application</title>
- <link rel="stylesheet" href="/webjars/bootstrap/3.3.7/css/bootstrap-theme.min.css"/>
- <link rel="stylesheet" href="/webjars/bootstrap/3.3.7/css/bootstrap.min.css"/>
- </head>
- <body>
- <nav class="navbar navbar-default">
- <div class="container-fluid">
- <div class="navbar-header">
- <a class="navbar-brand" href="/">Spring Trading application</a>
- </div>
- <div id="navbar" class="navbar-collapse collapse">
- <ul class="nav navbar-nav">
- <li><a href="/">Home</a></li>
- <li><a href="/quotes">Quotes</a></li>
- <li class="active"><a href="/websocket">Websocket</a></li>
- </ul>
- </div>
- </div>
- </nav>
- <div class="container wrapper">
- <h2>Websocket Echo</h2>
- <form class="form-inline">
- <div class="form-group">
- <input class="form-control" type="text" id="input" value="type something">
- <input class="btn btn-default" type="submit" id="button" value="Send"/>
- </div>
- </form>
- <div id="output"></div>
- </div>
- <script type="text/javascript" src="/webjars/jquery/1.11.1/jquery.min.js"></script>
- <script type="text/javascript" src="/webjars/bootstrap/3.3.7/js/bootstrap.min.js"></script>
- <script type="text/javascript">
- $(document).ready(function () {
- if (!("WebSocket" in window)) WebSocket = MozWebSocket;
- var socket = new WebSocket("ws://localhost:8080/websocket/echo");
- socket.onopen = function (event) {
- var newMessage = document.createElement('p');
- newMessage.textContent = "-- CONNECTED";
- document.getElementById('output').appendChild(newMessage);
- socket.onmessage = function (e) {
- var newMessage = document.createElement('p');
- newMessage.textContent = "<< SERVER: " + e.data;
- document.getElementById('output').appendChild(newMessage);
- }
- $("#button").click(function (e) {
- e.preventDefault();
- var message = $("#input").val();
- socket.send(message);
- var newMessage = document.createElement('p');
- newMessage.textContent = ">> CLIENT: " + message;
- document.getElementById('output').appendChild(newMessage);
- });
- }
- });
- </script>
- </body>
- </html>
也可以使用WebSocketClient
写测试
- @RunWith(SpringRunner.class)
- @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
- public class EchoWebSocketHandlerTests {
- @LocalServerPort
- private String port;
- @Test
- public void echo() throws Exception {
- int count = ;
- Flux<String> input = Flux.range(, count).map(index -> "msg-" + index);
- ReplayProcessor<Object> output = ReplayProcessor.create(count);
- WebSocketClient client = new StandardWebSocketClient();
- client.execute(getUrl("/websocket/echo"),
- session -> session
- .send(input.map(session::textMessage))
- .thenMany(session.receive().take(count).map(WebSocketMessage::getPayloadAsText))
- .subscribeWith(output)
- .then())
- .block(Duration.ofMillis());
- assertEquals(input.collectList().block(Duration.ofMillis()), output.collectList().block(Duration.ofMillis()));
- }
- protected URI getUrl(String path) throws URISyntaxException {
- return new URI("ws://localhost:" + this.port + path);
- }
- }
springboot 使用webflux响应式开发教程(二)的更多相关文章
- springboot 使用webflux响应式开发教程(一)
什么是webFlux 左侧是传统的基于Servlet的Spring Web MVC框架,右侧是5.0版本新引入的基于Reactive Streams的Spring WebFlux框架,从上到下依次是R ...
- SpringBoot使用WebFlux响应式编程操作数据库
这一篇文章介绍SpringBoot使用WebFlux响应式编程操作MongoDb数据库. 前言 在之前一篇简单介绍了WebFlux响应式编程的操作,我们在来看一下下图,可以看到,在目前的Spring ...
- springboot2 webflux 响应式编程学习路径
springboot2 已经发布,其中最亮眼的非webflux响应式编程莫属了!响应式的weblfux可以支持高吞吐量,意味着使用相同的资源可以处理更加多的请求,毫无疑问将会成为未来技术的趋势,是必学 ...
- [转]springboot2 webflux 响应式编程学习路径
原文链接 spring官方文档 springboot2 已经发布,其中最亮眼的非webflux响应式编程莫属了!响应式的weblfux可以支持高吞吐量,意味着使用相同的资源可以处理更加多的请求,毫无疑 ...
- 《微信小程序七日谈》- 第二天:你可能要抛弃原来的响应式开发思维
<微信小程序七日谈>系列文章: 第一天:人生若只如初见: 第二天:你可能要抛弃原来的响应式开发思维: 第三天:玩转Page组件的生命周期: 第四天:页面路径最多五层?导航可以这么玩 上篇文 ...
- 基于screen.width的伪响应式开发
一.站在用户的角度看问题 一个用户,访问一个web页面的真实场景是怎样的呢? 下面是某用户访问某站点的一个场景: 1. 小明打开了自己的电脑,访问了鑫空间-鑫生活: 2. 小明体内洪荒之力无法控制,疯 ...
- 移动端开发之响应式开发和bootstrap基础
响应式开发 (就是利用媒体查询针对不同宽度的设备进行布局和样式的设置,从而设配不同设备的目的) 响应式布局容器响应式需要一个父级作为布局容器,来配合子级元素来实现变化效果 原理:不同屏幕下,通过媒体查 ...
- 带你玩转JavaWeb开发之五-如何完成响应式开发页面
响应式页面开发 使用BootStrap开发一个响应式的页面出来 响应式开发就是同一个页面在PC端与手机端Pad端显示不同的效果,以给用户更好的体验 需求分析 开发一套页面,让用户能够在PC端, Pad ...
- 移动端使用rem同时适应安卓ios手机原理解析,移动端响应式开发
rem单位大家可能已经很熟悉,rem是随着html的字体大小来显示代表宽度的方法,我们怎样进行移动端响应式开发呢 浏览器默认的字体大小为16px 及1rem 等于 16px 如果我们想要使1rem等于 ...
随机推荐
- 初识express
初识Express 1.简介: express是基于Nodejs平台的快速,开放,极简的web开发框架 2.安装 npm install express --save 3.Hello world: c ...
- golang (3) 编译不同的平台文件
Golang 支持在一个平台下生成另一个平台可执行程序的交叉编译功能. Mac下编译Linux, Windows平台的64位可执行程序: CGO_ENABLED=0 GOOS=linux GOARCH ...
- 小程序点击清除input内的内容不生效
如下图,点击右侧的按钮清除input的内容,当获取焦点时点击按钮是会穿透的清除不了input,使用cover-image和cover-view页面不起作用 解决办法:input在左侧,按钮在右侧使他们 ...
- lua-nginx-module模块常用命令
ngx.location.capture 用法: local res = ngx.location.capture(uri, options) 发起一个同步非阻塞的nginx子请求,uri是inter ...
- Robot Framework_Ride(Edit标签)
前言 RIDE 作为 Robot Framework 的“脸面”,虽然我们已经可以拿它来创建和运行测试了,但我们对它的认识并不全面,这一小节我们将了解这个工具的使用 Edit标签 下面我们来看一看测试 ...
- c++ 网络编程(九)LINUX/windows-IOCP模型 多线程超详细教程及多线程实现服务端
原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/9661012.html 先讲Linux下(windows下在后面可以直接跳到后面看): 一.线程 ...
- ES6基本语法之let和const
1.var可以重复声明 var a = 12; var a = 5; alert(a) //5 2.var无法限制修改 如:PI = 3.1415: 3.var没有块级作用域 { } 像: if(){ ...
- 关于DeferredResult的思考
使用SpringBoot搭建web程序,里面内置了tomcat,一般都不会关心内部实现机制,上来就可以写程序,并且可以跑起来.但是是思考了每次的请求是如何工作的. 简单的来讲就是tomcat是将每次请 ...
- 攻克数据库核心技术壁垒,实现百万级QPS的高吞吐
CynosDB是腾讯云自研的新一代高性能高可用的企业级分布式云数据库.融合了传统数据库.云计算与新硬件的优势,100%兼容开源数据库,百万级QPS的高吞吐,不限存储,价格仅为商用数据库的1/10. C ...
- HDU 5698——瞬间移动——————【逆元求组合数】
瞬间移动 Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total Submis ...