什么是webFlux

左侧是传统的基于Servlet的Spring Web MVC框架,右侧是5.0版本新引入的基于Reactive Streams的Spring WebFlux框架,从上到下依次是Router Functions,WebFlux,Reactive Streams三个新组件。

Router Functions: 对标@Controller,@RequestMapping等标准的Spring MVC注解,提供一套函数式风格的API,用于创建Router,Handler和Filter。
WebFlux: 核心组件,协调上下游各个组件提供响应式编程支持。
Reactive Streams: 一种支持背压(Backpressure)的异步数据流处理标准,主流实现有RxJava和Reactor,Spring WebFlux默认集成的是Reactor。
在Web容器的选择上,Spring WebFlux既支持像Tomcat,Jetty这样的的传统容器(前提是支持Servlet 3.1 Non-Blocking IO API),又支持像Netty,Undertow那样的异步容器。不管是何种容器,Spring WebFlux都会将其输入输出流适配成Flux<DataBuffer>格式,以便进行统一处理。

值得一提的是,除了新的Router Functions接口,Spring WebFlux同时支持使用老的Spring MVC注解声明Reactive Controller。和传统的MVC Controller不同,Reactive Controller操作的是非阻塞的ServerHttpRequest和ServerHttpResponse,而不再是Spring MVC里的HttpServletRequest和HttpServletResponse。

  1. @GetMapping("/reactive/restaurants")
  2. public Flux<Restaurant> findAll() {
  3. return restaurantRepository.findAll();
  4. }

可以看到主要变化就是在 返回的类型上Flux<Restaurant>

Flux和Mono 是 Reactor 中的流数据类型,其中Flux会发送多次,Mono会发送0次或一次

  1. 使用webflux需要具备的基础是Reactive programming 的理解。
  2. Reactor 的基础 熟练的java8 lambda使用

创建springboot应用
下面通过创建股票报价的demo来演示。

通过 https://start.spring.io 或idea自带功能创建springboot项目,groupId为io.spring.workshop,artifactId为 stock-quotes。

勾选 ReactiveWeb

修改 application.properties 配置文件,指定接口 8081

  1. server.port=

启动应用,成功后控制台输出日志

日志显示使用Netty而不是tomcat,后续会使用Tomcat

股票报价生成
定义实体

  1. @Data
  2. public class Quote {
  3.  
  4. private static final MathContext MATH_CONTEXT = new MathContext();
  5.  
  6. private String ticker;
  7.  
  8. private BigDecimal price;
  9.  
  10. private Instant instant;
  11.  
  12. public Quote() {
  13. }
  14.  
  15. public Quote(String ticker, BigDecimal price) {
  16. this.ticker = ticker;
  17. this.price = price;
  18. }
  19.  
  20. public Quote(String ticker, Double price) {
  21. this(ticker, new BigDecimal(price, MATH_CONTEXT));
  22. }
  23.  
  24. @Override
  25. public String toString() {
  26. return "Quote{" +
  27. "ticker='" + ticker + '\'' +
  28. ", price=" + price +
  29. ", instant=" + instant +
  30. '}';
  31. }
  32. }

定义生成器

  1. @Component
  2. public class QuoteGenerator {
  3.  
  4. private final MathContext mathContext = new MathContext();
  5.  
  6. private final Random random = new Random();
  7.  
  8. private final List<Quote> prices = new ArrayList<>();
  9.  
  10. /**
  11. * 生成行情数据
  12. */
  13. public QuoteGenerator() {
  14. this.prices.add(new Quote("CTXS", 82.26));
  15. this.prices.add(new Quote("DELL", 63.74));
  16. this.prices.add(new Quote("GOOG", 847.24));
  17. this.prices.add(new Quote("MSFT", 65.11));
  18. this.prices.add(new Quote("ORCL", 45.71));
  19. this.prices.add(new Quote("RHT", 84.29));
  20. this.prices.add(new Quote("VMW", 92.21));
  21. }
  22.  
  23. public Flux<Quote> fetchQuoteStream(Duration period) {
  24.  
  25. // 需要周期生成值并返回,使用 Flux.interval
  26. return Flux.interval(period)
  27. // In case of back-pressure, drop events
  28. .onBackpressureDrop()
  29. // For each tick, generate a list of quotes
  30. .map(this::generateQuotes)
  31. // "flatten" that List<Quote> into a Flux<Quote>
  32. .flatMapIterable(quotes -> quotes)
  33. .log("io.spring.workshop.stockquotes");
  34. }
  35.  
  36. /**
  37. * Create quotes for all tickers at a single instant.
  38. */
  39. private List<Quote> generateQuotes(long interval) {
  40. final Instant instant = Instant.now();
  41. return prices.stream()
  42. .map(baseQuote -> {
  43. BigDecimal priceChange = baseQuote.getPrice()
  44. .multiply(new BigDecimal(0.05 * this.random.nextDouble()), this.mathContext);
  45. Quote result = new Quote(baseQuote.getTicker(), baseQuote.getPrice().add(priceChange));
  46. result.setInstant(instant);
  47. return result;
  48. })
  49. .collect(Collectors.toList());
  50. }
  51. }

使用webflux创建web应用

webflux的使用有两种方式,基于注解和函数式编程。这里使用函数式编程,先贴代码:

创建QuoteHandler

  1. @Component
  2. public class QuoteHandler {
  3.  
  4. private final Flux<Quote> quoteStream;
  5.  
  6. public QuoteHandler(QuoteGenerator quoteGenerator) {
  7. this.quoteStream = quoteGenerator.fetchQuoteStream(ofMillis()).share();
  8. }
  9.  
  10. public Mono<ServerResponse> hello(ServerRequest request) {
  11. return ok().contentType(TEXT_PLAIN)
  12. .body(BodyInserters.fromObject("Hello Spring!"));
  13. }
  14.  
  15. public Mono<ServerResponse> echo(ServerRequest request) {
  16. return ok().contentType(TEXT_PLAIN)
  17. .body(request.bodyToMono(String.class), String.class);
  18. }
  19.  
  20. public Mono<ServerResponse> streamQuotes(ServerRequest request) {
  21. return ok()
  22. .contentType(APPLICATION_STREAM_JSON)
  23. .body(this.quoteStream, Quote.class);
  24. }
  25.  
  26. public Mono<ServerResponse> fetchQuotes(ServerRequest request) {
  27. int size = Integer.parseInt(request.queryParam("size").orElse(""));
  28. return ok()
  29. .contentType(APPLICATION_JSON)
  30. .body(this.quoteStream.take(size), Quote.class);
  31. }
  32. }

创建Router

  1. @Configuration
  2. public class QuoteRouter {
  3.  
  4. @Bean
  5. public RouterFunction<ServerResponse> route(QuoteHandler quoteHandler) {
  6. return RouterFunctions
  7. .route(GET("/hello").and(accept(TEXT_PLAIN)), quoteHandler::hello)
  8. .andRoute(POST("/echo").and(accept(TEXT_PLAIN).and(contentType(TEXT_PLAIN))), quoteHandler::echo)
  9. .andRoute(GET("/quotes").and(accept(APPLICATION_JSON)), quoteHandler::fetchQuotes)
  10. .andRoute(GET("/quotes").and(accept(APPLICATION_STREAM_JSON)), quoteHandler::streamQuotes);
  11. }
  12. }

需要注意的是在springboot中Handler和Router都需要打上@Configuration。

HTTP请求交由Router转发给对应的Handler,Handler处理请求,并返回Mono<ServerResponse>,这里的Router类似@RequestMapping,Handler类似Controller。这么理解非常容易。

运行项目,浏览器输入 http://localhost:8081/hello 或者 使用curl,即可收到 "Hello Spring!"的文本信息。

到目前为止,一个简单的webflux示例已经完成,但是还没有体现出它与传统模式有何不同。

下面我们来做一下测试:

  1. $ curl http://localhost:8081/echo -i -d "WebFlux workshop" -H "Content-Type: text/plain"
  2. HTTP/1.1 OK
  3. transfer-encoding: chunked
  4. Content-Type: text/plain
  5.  
  6. WebFlux workshop

还是没有区别T.T,看下一步。

  1. $ curl http://localhost:8081/quotes -i -H "Accept: application/stream+json"
  2. HTTP/1.1 OK
  3. transfer-encoding: chunked
  4. Content-Type: application/stream+json
  5.  
  6. {"ticker":"CTXS","price":82.77,"instant":"2018-05-15T06:45:51.261Z"}
  7. {"ticker":"DELL","price":64.83,"instant":"2018-05-15T06:45:51.261Z"}
  8. {"ticker":"GOOG","price":,"instant":"2018-05-15T06:45:51.261Z"}
  9. {"ticker":"MSFT","price":67.3,"instant":"2018-05-15T06:45:51.261Z"}
  10. {"ticker":"ORCL","price":48.1,"instant":"2018-05-15T06:45:51.261Z"}
  11. {"ticker":"RHT","price":85.1,"instant":"2018-05-15T06:45:51.261Z"}
  12. {"ticker":"VMW","price":92.24,"instant":"2018-05-15T06:45:51.261Z"}
  13. -------------------------------无敌分割线-------------------------------------
  14. {"ticker":"CTXS","price":85.7,"instant":"2018-05-15T06:45:52.260Z"}
  15. {"ticker":"DELL","price":64.12,"instant":"2018-05-15T06:45:52.260Z"}
  16. {"ticker":"GOOG","price":,"instant":"2018-05-15T06:45:52.260Z"}
  17. {"ticker":"MSFT","price":67.9,"instant":"2018-05-15T06:45:52.260Z"}
  18. {"ticker":"ORCL","price":46.43,"instant":"2018-05-15T06:45:52.260Z"}
  19. {"ticker":"RHT","price":86.8,"instant":"2018-05-15T06:45:52.260Z"}
  20. ...

上面的分割线是为了易于分辨人为加上去的,我们看到返回结果每隔一秒刷新一次,不终止的话会一直返回数据,传统的Request/Response是一次请求,一次返回。

注意是设置了Header Accept: application/stream+json ,

如果将Header设置为 Accept: application/json ,只会得到一次Response。

写测试
springboot的test模块包含WebTestClient,可以用来对webflux服务端进行测试。

  1. @RunWith(SpringRunner.class)
  2. // We create a `@SpringBootTest`, starting an actual server on a `RANDOM_PORT`
  3. @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
  4. public class StockQuotesApplicationTests {
  5.  
  6. // Spring Boot will create a `WebTestClient` for you,
  7. // already configure and ready to issue requests against "localhost:RANDOM_PORT"
  8. @Autowired
  9. private WebTestClient webTestClient;
  10.  
  11. @Test
  12. public void fetchQuotes() {
  13. webTestClient
  14. // We then create a GET request to test an endpoint
  15. .get().uri("/quotes?size=20")
  16. .accept(MediaType.APPLICATION_JSON)
  17. .exchange()
  18. // and use the dedicated DSL to test assertions against the response
  19. .expectStatus().isOk()
  20. .expectHeader().contentType(MediaType.APPLICATION_JSON)
  21. .expectBodyList(Quote.class)
  22. .hasSize()
  23. // Here we check that all Quotes have a positive price value
  24. .consumeWith(allQuotes ->
  25. assertThat(allQuotes.getResponseBody())
  26. .allSatisfy(quote -> assertThat(quote.getPrice()).isPositive()));
  27. }
  28.  
  29. @Test
  30. public void fetchQuotesAsStream() {
  31. List<Quote> result = webTestClient
  32. // We then create a GET request to test an endpoint
  33. .get().uri("/quotes")
  34. // this time, accepting "application/stream+json"
  35. .accept(MediaType.APPLICATION_STREAM_JSON)
  36. .exchange()
  37. // and use the dedicated DSL to test assertions against the response
  38. .expectStatus().isOk()
  39. .expectHeader().contentType(MediaType.APPLICATION_STREAM_JSON)
  40. .returnResult(Quote.class)
  41. .getResponseBody()
  42. .take()
  43. .collectList()
  44. .block();
  45.  
  46. assertThat(result).allSatisfy(quote -> assertThat(quote.getPrice()).isPositive());
  47. }
  48. }

参考文章:

https://docs.spring.io/spring-framework/docs/5.0.3.RELEASE/spring-framework-reference/web.html#web-reactive-server-functional
http://projectreactor.io/docs
https://www.ibm.com/developerworks/cn/java/spring5-webflux-reactive/index.html
https://blog.csdn.net/qq_34438958/article/details/78539234

springboot 使用webflux响应式开发教程(一)的更多相关文章

  1. springboot 使用webflux响应式开发教程(二)

    本篇是对springboot 使用webflux响应式开发教程(一)的进一步学习. 分三个部分: 数据库操作webservicewebsocket 创建项目,artifactId = trading- ...

  2. SpringBoot使用WebFlux响应式编程操作数据库

    这一篇文章介绍SpringBoot使用WebFlux响应式编程操作MongoDb数据库. 前言 在之前一篇简单介绍了WebFlux响应式编程的操作,我们在来看一下下图,可以看到,在目前的Spring ...

  3. springboot2 webflux 响应式编程学习路径

    springboot2 已经发布,其中最亮眼的非webflux响应式编程莫属了!响应式的weblfux可以支持高吞吐量,意味着使用相同的资源可以处理更加多的请求,毫无疑问将会成为未来技术的趋势,是必学 ...

  4. [转]springboot2 webflux 响应式编程学习路径

    原文链接 spring官方文档 springboot2 已经发布,其中最亮眼的非webflux响应式编程莫属了!响应式的weblfux可以支持高吞吐量,意味着使用相同的资源可以处理更加多的请求,毫无疑 ...

  5. 《微信小程序七日谈》- 第二天:你可能要抛弃原来的响应式开发思维

    <微信小程序七日谈>系列文章: 第一天:人生若只如初见: 第二天:你可能要抛弃原来的响应式开发思维: 第三天:玩转Page组件的生命周期: 第四天:页面路径最多五层?导航可以这么玩 上篇文 ...

  6. 带你玩转JavaWeb开发之五-如何完成响应式开发页面

    响应式页面开发 使用BootStrap开发一个响应式的页面出来 响应式开发就是同一个页面在PC端与手机端Pad端显示不同的效果,以给用户更好的体验 需求分析 开发一套页面,让用户能够在PC端, Pad ...

  7. 移动端使用rem同时适应安卓ios手机原理解析,移动端响应式开发

    rem单位大家可能已经很熟悉,rem是随着html的字体大小来显示代表宽度的方法,我们怎样进行移动端响应式开发呢 浏览器默认的字体大小为16px 及1rem 等于 16px 如果我们想要使1rem等于 ...

  8. BootStrap常用组件及响应式开发

    BootStrap常用组件 PS:所有的代码必须写在<class="container/container-fluid">容器当中 常用组件包含内容: 字体图标 下拉菜 ...

  9. 借鉴bootstrap的方法,快速实现响应式开发

    响应式开发 注意:任何框架都是一个辅助手段,只需借鉴其中的核心思想,把其中核心的东西提炼出来即可.不要因为,提到响应式就想到只能够用bootstrap来实现,框架现有的东西是可以提高我们的效率,但是其 ...

随机推荐

  1. 数组模拟单向链表例题(UVa11988)

    指针的链表实现方式是,当前节点的next指向下一个节点,用数组模拟就是 for(int i=next[0];i!=0;i=next[i]) i=next[i]:就是一条链. 例题: 你有一个破损的键盘 ...

  2. vue 初步了解provide/inject

    provider/inject:简单的来说就是在父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量. 需要注意的是 provide / inject这对选项需要一起使用, ...

  3. 【OpenCV-Python】-图像阀值

    参考:Opencv官方教程 1.简单阀值 cv2.threshold , cv2.adaptiveThreshold当像素值高于阀值时,我们给这个像素赋予一个新值(可能是白色),否则我们给它赋予另外一 ...

  4. WCF系列教程之WCF消息交换模式之单项模式

    1.使用WCF单项模式须知 (1).WCF服务端接受客户端的请求,但是不会对客户端进行回复 (2).使用单项模式的服务端接口,不能包含ref或者out类型的参数,至于为什么,请参考C# ref与out ...

  5. jedis入门教程

    1 jedis介绍 2 java连接Redis 1 导入jar包 2 连接实例 @Test //获得单一的jedis对象操作数据库 public void test1(){ //1.获得连接对象 设置 ...

  6. Android开发环境搭建步骤-【Android】

    本教程是android开发环境在windows下的安装配置,经本人测试完全正确无误.这个教程是史上最详细的android开发环境搭建教程. 工具/原料 Eclipse 3.7.0.Java Jdk6. ...

  7. unity换装系统+网格合并

    这里的做法是模型把所有衣服全部穿上作为一个资源 然后还有一个只有骨骼信息的骨架资源 将这2个制作好了Prefab 模型部件数据 资源数据 [代码] using System.Collections; ...

  8. 【.Net】 C#参数数组与函数重载

    static int ParamsFunc(int i, string s) { return i; } static int ParamsFunc(int i, string s, params i ...

  9. FZU 2139——久违的月赛之二——————【贪心】

    久违的月赛之二 Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Submit Stat ...

  10. Spring ContextLoaderListener

    ContextLoaderListener的作用就是启动Web容器时,自动装配ApplicationContext的配置信息.因为它实现了ServletContextListener这个接口,在web ...