搜索词>>WebFlux 耗时0.0040
  • Spring WebFlux

    Spring WebFlux,spring框架5.0将会新增的web增强框架,这里主要讲述什么是Spring WebFlux以及Spring WebFlux的新功能,Spring WebFlux与Spring MVC的关系。<h2><img alt="spring boot2.0 spring5.0" class="img-thumbnail" src="/resources/assist/images/blog/760dfcfe492b4e4b8c5fa8f2f899ea29.png" /><br /> 1.Spring WebFlux简介</h2>   包含在Spring框架中的原始Web框架Spring Web MVC是为Servlet API和Servlet容器而设计的。 反过来的堆栈,Web框架,Spring WebFlux,稍后在版本5.0中添加。 它是完全非阻塞的,支持反向流反向压力,并在诸如Netty,Undertow和Servlet 3.1+容器之类的服务器上运行。<br /> <br />   这两个Web框架镜像了Spring模块的名称spring-webmvc和spring-webflux,并在Spring框架中并存。 每个模块都是可选的。 应用可以使用一个或另一个模块,或者在一些情况下可以使用例如。 具有反应WebClient的Spring MVC控制器。<br /> <br />   除了Web框架之外,Spring WebFlux还提供了用于执行HTTP请求的WebClient,用于测试Web端点的WebTestClient以及客户端和服务器反应的WebSocket支持。 <h2>2.为何要新增一个web框架?</h2>   答案的一部分是需要一个非阻塞的Web栈来处理与少量线程的并发,并以较少的硬件资源进行扩展。 Servlet 3.1确实为非阻塞I / O提供了API。但是,使用它会远离Servlet API的其他部分,其中合同是同步的(Filter,Servlet)或阻塞(getParameter,getPart)。这是新的通用API作为任何非阻塞运行时基础的动机。这一点很重要,因为Netty这样的服务器在异步非阻塞空间中已经建立起来了。<br /> <br />   答案的另一部分是功能编程。很像在Java 5中添加注释创造的机会 - 例如。注释的REST控制器或单元测试,在Java 8中添加lambda表达式为Java中的功能API创造了机会。这对于非阻塞应用程序和连续样式API是一个福音 - 由CompletableFuture和ReactiveX普及,允许异步逻辑的声明性组合。在编程模型级别,Java 8启用了Spring WebFlux,可以为注释控制器提供功能性的Web端点。 <h2>3.Reactive什么和为什么?</h2>   我们触及非阻塞和功能,但为什么反应性,我们的意思是什么?<br /> <br />   术语“反应性”是指围绕响应变化构建的编程模型 - 网络组件对I / O事件的响应,UI控制器对鼠标事件的反应等。在这个意义上,非阻塞是反应的,因为不是被阻止现在在作为操作完成或数据变得可用的响应通知的模式。<br /> <br />   还有一个重要的机制,我们在春季团队与“反应”相关联,这是非阻碍的背压。在同步的命令式代码中,阻止调用可以作为强制呼叫者等待的背部压力的自然形式。在非阻塞代码中,重要的是控制事件的速率,以便快速生产者不会压倒其目的地。<br /> <br />   活动流是一种小规模,也是Java 9中采用的,它定义了具有背压的异步组件之间的交互。例如,作为发布者的数据存储库可以产生数据,作为订阅者的HTTP服务器可以写入响应。活动流的主要目的是允许用户控制发布商产生数据的速度或速度。<br /> <br /> <strong>提示:</strong><br /> 常见的问题:如果发布商不能放慢下去呢?<br /> 活动流的目的只是建立机制和边界。 如果发布商不能放慢速度,那么它必须决定是缓冲还是丢弃还是失败。 <h2>4.Reactive API</h2>   活动流对互操作性起着重要作用。图书馆和基础架构组件感兴趣,但作为应用程序API不太有用,因为它的级别太低。什么应用程序需要更高级别和更丰富的功能API来构成异步逻辑 - 类似于Java 8 Stream API,但不仅仅是集合。这是反应库的作用。<br /> <br />   反应堆是Spring WebFlux选择的活动库。它提供了Mono和Flux API类型,可以通过与运算符的ReactiveX词汇对齐的丰富的运算符来处理0..1和0..N的数据序列。反应堆是一个反应流库,因此所有的运营商都支持非阻塞的背压。反应堆非常重视服务器端的Java。它与Spring紧密合作开发。<br /> <br />   WebFlux要求Reactor作为核心依赖关系,但它可以通过Reactive Streams与其他反应库互操作。作为一般规则,WebFlux API接受普通的Publisher作为输入,在内部适应Reactor类型,使用它们,然后返回Flux或Mono作为输出。所以您可以将任何Publisher作为输入传递,您可以对输出应用操作,但是您需要调整输出以与另一个活动库一起使用。无论何时 - 例如注释控制器,WebFlux透明地适用于使用RxJava或其他反应库。有关详细信息,请参阅反应库。 <h2>5.编程模型</h2> Spring-Web模块包含基于Spring WebFlux - HTTP抽象,Reactive Streams服务器适配器,反应式编解码器和核心Web API的反应性基础,其角色与Servlet API相当,但具有非阻塞语义。<br /> <br /> 在此基础上,Spring WebFlux提供了两种编程模型的选择: <ul> <li>注释控制器 - 与Spring MVC相一致,并且基于与spring-web模块相同的注释。 Spring MVC和WebFlux控制器都支持反应(Reactor,RxJava)返回类型,因此不容易将它们分开。一个显着的区别是,WebFlux还支持反应性@RequestBody参数。</li> <li>功能端点 - 基于lambda的轻量级功能编程模型。将其视为小型库或应用程序可用于路由和处理请求的一组实用程序。注释控制器的巨大差异在于,应用程序负责从开始到结束的请求处理,并通过注释声明意图并被回调。</li> </ul> <h2>6.如何选择一个web框架?</h2>   你应该使用Spring MVC还是WebFlux?我们来看几个不同的观点。<br /> <br />   如果您有一个Spring MVC应用程序工作正常,则不需要更改。命令编程是编写,理解和调试代码的最简单方法。您最多可以选择库,因为历史上绝大多数都是阻止。<br /> <br />   如果您已经购买了一个非阻塞的Web堆栈,Spring WebFlux可以提供与这个空间中的其他人一样的执行模式优势,并且还提供了一些选择的服务器 - Netty,Tomcat,Jetty,Undertow,Servlet 3.1+容器,编程模型 - 注释控制器和功能Web端点,以及可选的反应库 - 反应器,RxJava或其他。<br /> <br />   如果您对使用Java 8 lambdas或Kotlin的轻量级功能Web框架感兴趣,则可以使用Spring WebFlux功能Web端点。对于具有较少复杂要求的较小应用或微服务,这也可能是更好的选择,可以从更大的透明度和控制中受益。<br /> <br />   在微服务架构中,您可以使用Spring MVC或Spring WebFlux控制器或Spring WebFlux功能端点组合应用程序。在两个框架中支持相同的基于注释的编程模型,使得重新使用知识更容易,同时为正确的工作选择正确的工具。<br /> <br />   评估应用程序的一种简单方法是检查其依赖性。如果您使用阻塞持久性API(JPA,JDBC)或网络API,则Spring MVC至少是常见架构的最佳选择。在技​​术上,Reactor和RxJava都可以在单独的线程上执行阻塞调用,但是您不会充分利用非阻塞的Web堆栈。<br /> <br />   如果您有一个Spring MVC应用程序调用远程服务,请尝试反向WebClient。您可以从Spring MVC控制器方法直接返回反应类型(Reactor,RxJava或其他)。每个呼叫的延迟或呼叫之间的相互依赖性越大,益处越大。 Spring MVC控制器也可以调用其他无效组件。<br /> <br />   如果你有一个庞大的团队,记住转向非阻塞,功能和声明性编程的陡峭的学习曲线。在没有完全切换的情况下启动的实际方法是使用反应性WebClient。除了这个开始之外,测量的好处。我们预计,对于广泛的应用,转移是不必要的。<br /> <br />   如果您不确定要查找哪些优点,首先了解非阻塞性I / O的工作原理(例如,单线程Node.js上的并发性不是矛盾)及其影响。标签行是“具有较少硬件的缩放”,但不能保证效果,而不是一些网络I / O可能会变慢或不可预测。这个Netflix博客文章是一个很好的资源。 <h2>7.Spring WebFlux Server的选择</h2>   Netnet,Undertow,Tomcat,Jetty和Servlet 3.1+容器支持Spring WebFlux。 每个服务器都适用于一个通用的Reactive Streams API。 Spring WebFlux编程模型基于该通用API。<br />    <strong>提示:</strong><br /> <em>  常见的问题:Tomcat和Jetty如何在两个堆栈中使用?<br />   Tomcat和Jetty的核心是无阻拦。 这是Servlet API,它添加了一个阻塞的外观。 从版本3.1开始,Servlet API为非阻塞I / O添加了一个选择。 然而,其使用需要注意避免<br />   其他同步和阻塞部件。 因此,Spring的反应式Web堆栈具有低级Servlet适配器来桥接到活动流,但是Servlet API否则不会直接使用。</em><br /> <br />   默认情况下,Spring Boot 2使用Netty WebFlux,因为Netty在异步非阻塞空间中被广泛使用,并且还提供可共享资源的客户端和服务器。 通过比较Servlet 3.1非阻塞I / O没有太多的使用,因为使用它的条数是如此之高。 Spring WebFlux打开了一条实用的通路。<br /> <br />   Spring Boot中的默认服务器选择主要是关于开箱即用的体验。 应用程序仍然可以选择任何其他受支持的服务器,这些服务器也对性能进行了高度优化,完全无阻塞,并适应了反应流反向压力。 在Spring Boot中,进行切换是微不足道的。 <h2>8.性能与规模</h2>   表现有很多特点和意义。 反应和非阻塞通常不会使应用程序运行更快。 在某些情况下,他们可以使用WebClient来并行执行远程调用。 总的来说,需要更多的工作来处理非阻塞的方式,并且可以稍微增加所需的处理时间。<br /> <br />   反应和非阻塞的关键预期好处是能够以小的固定数量的线程和较少的内存进行扩展。 这使得应用程序在负载下更具弹性,因为它们以更可预测的方式扩展。 为了观察这些好处,您需要有一些延迟,包括慢速和不可预测的网络I / O的混合。 这是反应堆栈开始显示其优势的地方,差异可能很大。
  • Spring WebFlux 项目实战 在Spring WebFlux中创建多个RouterFunctions

    Spring WebFlux 项目实战 在Spring WebFlux中创建多个RouterFunctions,在这篇文章中,我们将着眼于在Spring WebFlux中将多个路由器功能定义到不同的逻辑域。<h2>引言</h2> <blockquote> <p>在这篇文章中,我们将着眼于在Spring WebFlux中将多个路由器功能定义到不同的逻辑域。如果您创建“微服务”,这可能不会成为问题,因为您很可能只在每个服务的单个域中工作,但如果您不是,那么您可能需要在应用程序中包含多个域用户或您自己的服务可以与之交互。这样做的代码就像我希望的那样简单,可以用几句话来解释。为了使这篇文章更有趣一些,我们将看一些使这一切成为可能的Spring代码。</p> </blockquote> 如果你是WebFlux的新手,我推荐看看我以前的博客,[<a rel="" target="_blank"href="http://www.leftso.com/blog/379.html" rel="" target="_blank">Spring WebFlux 项目实战</a>],在那里我写了一些关于这个主题的完整例子和解释。<br />   <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">所以我们先假设置场景。你的应用程序中有两个不同的域,例如人员和位置。你可能想要让它们不仅在逻辑上而且在你的代码中彼此分离。要做到这一点,您需要一种方法来定义与其他域相互隔离的路由。这是我们将在这篇文章中看到的内容。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">如果你认为你已经知道这个问题的答案,那么你可能是对的。这真的很简单。尽管如此,让我们继续努力吧。要为人员域创建路由,请创建一个<code>RouterFunction</code>映射到相关处理函数的bean,如下所示。</span></span></span></p> <pre> <code class="language-java">@Configuration public class MyRouter { // works for a single bean @Bean public RouterFunction<ServerResponse> routes(PersonHandler personHandler) { return RouterFunctions.route(GET("/people/{id}").and(accept(APPLICATION_JSON)), personHandler::get) .andRoute(GET("/people").and(accept(APPLICATION_JSON)), personHandler::all) .andRoute(POST("/people").and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::post) .andRoute(PUT("/people/{id}").and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::put) .andRoute(DELETE("/people/{id}"), personHandler::delete) .andRoute(GET("/people/country/{country}").and(accept(APPLICATION_JSON)), personHandler::getByCountry); } }</code></pre> <br /> 这将创建到各种处理函数的路由<code>PersonHandler</code>。<br /> 所以,现在我们要添加位置逻辑的路由。我们可以简单地将路由添加到此bean,如下所示。 <pre> <code class="language-java">@Configuration public class MyRouter { // not ideal! @Bean public RouterFunction<ServerResponse> routes(PersonHandler personHandler, LocationHandler locationHandler) { return RouterFunctions.route(GET("/people/{id}").and(accept(APPLICATION_JSON)), personHandler::get) .andRoute(GET("/people").and(accept(APPLICATION_JSON)), personHandler::all) .andRoute(POST("/people").and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::post) .andRoute(PUT("/people/{id}").and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::put) .andRoute(DELETE("/people/{id}"), personHandler::delete) .andRoute(GET("/people/country/{country}").and(accept(APPLICATION_JSON)), personHandler::getByCountry) .andRoute(GET("/locations/{id}").and(accept(APPLICATION_JSON)), locationHandler::get); } }</code></pre> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">这个bean现在包含一个引用,<code>LocationHandler</code>以便可以设置位置路由。这个解决方案的问题是它需要将代码耦合在一起。而且,如果你需要添加更多的处理程序,你很快就会被注入到这个bean中的依赖关系的数量所淹没。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">解决这个问题的方法是创建多个<code>RouterFunction</code>bean。而已。因此,如果我们在人员域中创建一个,<code>PersonRouter</code>并且在位置域中指定一个<code>LocationRouter</code>,则每个人都可以定义他们需要的路由,而Spring将完成剩下的任务。这是有效的,因为Spring遍历应用程序上下文并找到或创建任何<code>RouterFunction</code>bean并将它们合并为一个函数供以后使用。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">使用这些信息,我们可以编写下面的代码。</span></span></span><br />  </p> <pre> <code class="language-java">@Configuration public class PersonRouter { // solution @Bean public RouterFunction<ServerResponse> peopleRoutes(PersonHandler personHandler) { return RouterFunctions.route(GET("/people/{id}").and(accept(APPLICATION_JSON)), personHandler::get) .andRoute(GET("/people").and(accept(APPLICATION_JSON)), personHandler::all) .andRoute(POST("/people").and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::post) .andRoute(PUT("/people/{id}").and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::put) .andRoute(DELETE("/people/{id}"), personHandler::delete) .andRoute(GET("/people/country/{country}").and(accept(APPLICATION_JSON)), personHandler::getByCountry); } }</code></pre> 和 <pre> <code class="language-java">@Configuration public class LocationRouter { // solution @Bean public RouterFunction<ServerResponse> locationRoutes(LocationHandler locationHandler) { return RouterFunctions.route(GET("/locations/{id}").and(accept(APPLICATION_JSON)), locationHandler::get); } }</code></pre> <code>PersonRouter</code>可以与其他人/与人相关的代码保存,并<code>LocationRouter</code>可以做同样的事情。<br />   <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">为了让这个更有趣,为什么这个工作?</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff"><code>RouterFunctionMapping</code>是检索<code>RouterFunction</code>应用程序上下文中创建的所有bean 的类。该<code>RouterFunctionMapping</code>豆创建中<code>WebFluxConfigurationSupport</code>这是春天WebFlux配置震中。通过<code>@EnableWebFlux</code>在配置类中包含注释或者依靠自动配置,一系列事件开始并收集我们所有的<code>RouterFunction</code>s就是其中之一。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">下面是这<code>RouterFunctionMapping</code>堂课。我已经删除了它的构造函数和一些方法来使这里的代码片段更容易消化。</span></span></span></p> <pre> <code class="language-java">public class RouterFunctionMapping extends AbstractHandlerMapping implements InitializingBean { @Nullable private RouterFunction<?> routerFunction; private List<HttpMessageReader<?>> messageReaders = Collections.emptyList(); // constructors // getRouterFunction // setMessageReaders @Override public void afterPropertiesSet() throws Exception { if (CollectionUtils.isEmpty(this.messageReaders)) { ServerCodecConfigurer codecConfigurer = ServerCodecConfigurer.create(); this.messageReaders = codecConfigurer.getReaders(); } if (this.routerFunction == null) { initRouterFunctions(); } } /** * Initialized the router functions by detecting them in the application context. */ protected void initRouterFunctions() { if (logger.isDebugEnabled()) { logger.debug("Looking for router functions in application context: " + getApplicationContext()); } List<RouterFunction<?>> routerFunctions = routerFunctions(); if (!CollectionUtils.isEmpty(routerFunctions) && logger.isInfoEnabled()) { routerFunctions.forEach(routerFunction -> logger.info("Mapped " + routerFunction)); } this.routerFunction = routerFunctions.stream() .reduce(RouterFunction::andOther) .orElse(null); } private List<RouterFunction<?>> routerFunctions() { SortedRouterFunctionsContainer container = new SortedRouterFunctionsContainer(); obtainApplicationContext().getAutowireCapableBeanFactory().autowireBean(container); return CollectionUtils.isEmpty(container.routerFunctions) ? Collections.emptyList() : container.routerFunctions; } // getHandlerInternal private static class SortedRouterFunctionsContainer { @Nullable private List<RouterFunction<?>> routerFunctions; @Autowired(required = false) public void setRouterFunctions(List<RouterFunction<?>> routerFunctions) { this.routerFunctions = routerFunctions; } } }</code></pre> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">检索所有路由的路径开始于创建bean <code>afterPropertiesSet</code>后调用<code>RouterFunctionMapping</code>。由于它是内部的<code>RouterFunction</code>,<code>null</code>它会<code>initRouterFunctions</code>触发一系列导致执行的方法<code>routerFunctions</code>。一个新<code>SortedRouterFunctionsContainer</code>的构造(私有静态类)<code>routerFunctions</code>通过注入<code>RouterFunction</code>应用程序上下文中的所有s来设置它的字段。这工作,因为Spring会注入类型的所有豆类<code>T</code>一个当<code>List<T></code>被注入。现在检索到的<code>RouterFunction</code>s被组合在一起以制作一个<code>RouterFunction</code>从现在开始用于将所有传入请求路由到适当处理程序的单个节点。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">这里的所有都是它的。总而言之,<code>RouterFunction</code>为不同业务领域定义多个是非常简单的,因为您只需在最有意义的任何区域创建它们,Spring就会关闭并获取所有区域。为了使我们研究的一些魔法神秘化,<code>RouterFunctionMapping</code>以了解<code>RouterFunction</code>我们创建的s是如何收集和组合的,以便它们可以用来将请求路由到处理程序。作为结束语,我确实明白这篇文章在某些方面是相当微不足道的,但有时看起来很明显的信息会非常有帮助。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">我建议看看我以前的帖子[<a rel="" target="_blank"href="http://www.leftso.com/blog/379.html" rel="" target="_blank">Spring WebFlux 项目实战</a>]</span></span></span></p>
  • spring boot webflux client实战

    spring boot webflux client实战,webclient是spring webflux的一个小组件。对于Java的http通讯来说,webclient是非常简单易用的。<h2>引言</h2>     在spring 5.0发布的同时发布了webflux功能类似spring mvc。底层实现以及关注点不同。今天我们主要讲解spring webflux中的一个组件webclient。webclient是spring webflux的一个小组件。对于Java的http通讯来说,webclient是非常简单易用的。比起apache的httpclient组件更方便的集成到项目中 <h2>一.创建一个spring boot 项目包含webflux组件</h2> <h3>1.1创建Spring  boot项目并加入webflux组件依赖</h3> <img alt="选择依赖" class="img-thumbnail" src="/resources/assist/images/blog/039cefbac57f44eaa8bed5f3e1ff1bd2.png" /> <h2>1.2项目结构图展示</h2> <img alt="项目结构图" class="img-thumbnail" src="/resources/assist/images/blog/dbc64afd017e47e2a14e62f2c65fc74d.png" /> <h2>二.创建一个间的web服务</h2> <h3>2.1创建spring webflux基于Java的配置</h3> WebConfig: <pre> <code class="language-java">package net.xqlee.project.config; import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.format.datetime.DateFormatter; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.config.ResourceHandlerRegistry; import org.springframework.web.reactive.config.WebFluxConfigurer; /** * 基于Java代码配置启用Spring WebFlux * * @author xqlee * */ @Configuration @EnableWebFlux public class WebConfig implements WebFluxConfigurer { String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; /**** * 配置常用的转换器和格式化配置(与Spring MVC 5配置方式一样) */ @Override public void addFormatters(FormatterRegistry registry) { // 添加日期格式化转换 DateFormatter dateFormatter = new DateFormatter(DATE_FORMAT); registry.addFormatter(dateFormatter); } /**** * 资源路径映射配置(与Spring MVC 5一样,只是引入的类不同) */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/resources/**").addResourceLocations("/public", "classpath:/static/"); } } </code></pre> 通过spring 官方文档查询可以知道,spring webflux的配置使用和Spring mvc的配置使用方法基本相同。上面只是个简单的配置。 <h3>2.2创建一个controller用来做webclient的测试请求服务</h3> TestController: <pre> <code class="language-java">package net.xqlee.project.controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; import net.xqlee.project.pojo.Person; @Controller public class TestController { private static final Logger log = LoggerFactory.getLogger(TestController.class); @PostMapping("persion/getPersion/{id}.json") @ResponseBody public Person getPersion(@PathVariable("id") String id) { log.info("ID:" + id); return new Person("1", "leftso", 1, "重庆.大竹林"); } } </code></pre> 上面包含一个简单对象Persion: <pre> <code class="language-java">package net.xqlee.project.pojo; public class Person { public Person() { } public Person(String id, String name, int age, String address) { super(); this.id = id; this.name = name; this.age = age; this.address = address; } String id; String name; int age; String address; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } } </code></pre> 好了一个spring boot的webflux服务已经创建完毕。接下来将进行webclient的调用方法。 <h2>三.创建webclient</h2> 3.1创建一个测试的类 <pre> <code class="language-java">package net.xqlee.project; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.client.WebClient; import net.xqlee.project.pojo.Person; import reactor.core.publisher.Mono; /** * reactive webClient * * @author xqlee * */ public class TestClient { private static final Logger log = LoggerFactory.getLogger(TestClient.class); public static void main(String[] args) { WebClient client = WebClient.create("http://localhost:8080/"); Mono<Person> result = client.post()// 请求方法,get,post... .uri("persion/getPersion/{id}.json", "123")// 请求相对地址以及参数 .accept(MediaType.APPLICATION_JSON).retrieve()// 请求类型 .bodyToMono(Person.class);// 返回类型 Person person = result.block(); log.info(JSONObject.wrap(person).toString()); } } </code></pre> 上面已经创建了一个webclient的请求。请求的地址是刚才我们创建的一个简单的测试服务。<br /> 首先运行spring boot项目启动测试服务: <pre> <code class="language-html"> . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.0.0.M6) 2017-11-19 13:53:09.290 INFO 6976 --- [ main] n.x.p.DemoSpringbootWebflux2Application : Starting DemoSpringbootWebflux2Application on DESKTOP-2RG0A6O with PID 6976 (D:\workplace\eclipse_mvn\demo-springboot-webflux-2\target\classes started by xqlee in D:\workplace\eclipse_mvn\demo-springboot-webflux-2) 2017-11-19 13:53:09.295 INFO 6976 --- [ main] n.x.p.DemoSpringbootWebflux2Application : No active profile set, falling back to default profiles: default 2017-11-19 13:53:09.377 INFO 6976 --- [ main] .r.c.ReactiveWebServerApplicationContext : Refreshing org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext@59402b8f: startup date [Sun Nov 19 13:53:09 CST 2017]; root of context hierarchy 2017-11-19 13:53:10.480 INFO 6976 --- [ main] s.w.r.r.m.a.RequestMappingHandlerMapping : Mapped "{[/persion/getPersion/{id}.json],methods=[POST]}" onto public net.xqlee.project.pojo.Person net.xqlee.project.controller.TestController.getPersion(java.lang.String) 2017-11-19 13:53:10.542 INFO 6976 --- [ main] o.s.w.r.handler.SimpleUrlHandlerMapping : Mapped URL path [/resources/**] onto handler of type [class org.springframework.web.reactive.resource.ResourceWebHandler] 2017-11-19 13:53:10.611 INFO 6976 --- [ main] o.s.w.r.r.m.a.ControllerMethodResolver : Looking for @ControllerAdvice: org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext@59402b8f: startup date [Sun Nov 19 13:53:09 CST 2017]; root of context hierarchy 2017-11-19 13:53:11.256 INFO 6976 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup 2017-11-19 13:53:11.523 INFO 6976 --- [ctor-http-nio-1] r.ipc.netty.tcp.BlockingNettyContext : Started HttpServer on /0:0:0:0:0:0:0:0:8080 2017-11-19 13:53:11.524 INFO 6976 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8080 2017-11-19 13:53:11.530 INFO 6976 --- [ main] n.x.p.DemoSpringbootWebflux2Application : Started DemoSpringbootWebflux2Application in 2.554 seconds (JVM running for 3.059) </code></pre> 通过启动日志我们可以看到。默认情况下webflux已经不是使用tomcat容器启动项目。而是替换为netty容器启动的项目。<br /> <br /> 运行我们上面创建的webclient:<br /> 重点截图1:<br /> <img alt="webclient" class="img-thumbnail" src="/resources/assist/images/blog/b317c94a94254e9a9560f4d3374dc25c.png" />这里可以看到打印的请求日志http相关的头部信息<br /> <br /> 重点截图2:<br /> <img alt="webclient执行结果" class="img-thumbnail" src="/resources/assist/images/blog/322c8a6cf0e941f7ab70b1829a62718e.png" />上面可以看到调用的返回已经自动载入Persion对象。<br /> <br /> 通过上面的使用不难发现使用weblcient在Java中进行http通讯是非常方便的。而且支持nio异步操作。在效率上也应该有所提升。在使用微服务的时候,是否可以考虑服务之间通讯使用weblcient?
  • Spring WebFlux教程Hello Word

    Spring WebFlux入门程序hello word。本文主要在于讲解如何创建和运行spring webflux入门程序hello word。其实不难发现和spring mvc相比代码层基本没区别的<h2>1.创建一个spring boot2.0项目并包含spring webflux模块</h2> <br /> eclipse中创建首先需要安装spring的STS工具,之前讲过可以搜索一下。这里直接开始创建项目<br /> <img alt="创建项目第一步" class="img-thumbnail" src="/resources/assist/images/blog/78cd327eb5d443f19b18a222ab6e6f44.png" /><br /> <br /> 注意选择spring boot版本2.0,由于现在还没有正式版本。选择M5版本<br /> <img alt="选择spring boot版本" class="img-thumbnail" src="/resources/assist/images/blog/0b9457cd7aee48379991634b8c81c00f.png" /><br />   <h2>2.项目结构图</h2> <img alt="项目结构图" class="img-thumbnail" src="/resources/assist/images/blog/eec0271552ee47c7a034196b74d07dc3.png" /><br /> 不难发现和之前的1.0没啥区别 <h2>3.Spring WebFlux编写Hello Word</h2> <em><strong>HelloController.java</strong></em> <pre> <code class="language-java">package com.example; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @GetMapping("/hello") public String handle() { return "Hello WebFlux"; } } </code></pre> <br /> 运行Application类与1.x版本一样,启动spring boot项目<br /> 访问地址:http://localhost:8080/hello<br /> <img alt="hello" class="img-thumbnail" src="/resources/assist/images/blog/a77ec83134764d4b8f3ef966ba10b5e6.png" /><br /> <br /> 好啦就这么简单,后面继续深入学习。<br /> <br />  
  • Spring WebFlux 项目实战 spring boot 2.0正式版

    Spring WebFlux 项目实战 spring boot 2.0正式版 源码下载。Spring Boot 2.0最近去了GA,所以我决定写我关于Spring的第一篇文章很长一段时间。自发布以来,我一直在看到越来越多的Spring WebFlux以​​及如何使用它的教程。<h2>引言</h2> <blockquote> <p>Spring Boot 2.0最近去了GA,所以我决定写我关于Spring的第一篇文章很长一段时间。自发布以来,我一直在看到越来越多的Spring WebFlux以​​及如何使用它的教程。但是在阅读完它们并尝试让它自己工作之后,我发现从包含在我阅读的文章和教程中的代码跳转到编写实际上比返回字符串更有趣的事情从后端。现在,我希望我不会在自己的脚下说自己可能会对我在这篇文章中使用的代码做出同样的批评,但这里是我试图给Spring WebFlux的教程,它实际上类似于你可能会在野外使用的东西。</p> </blockquote> <span style="color:#ff0000">项目结构:<br /> <img srcset="" width="" size="" class="img-thumbnail" alt="项目结构图" src="/resources/assist/images/blog/5b14b7c8551a41389f790ace59443849.jpg" /></span> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">在我继续之前,在提及WebFlux之后,究竟是什么呢?Spring WebFlux是Spring MVC的完全非阻塞反应式替代方案。它允许更好的垂直缩放而不增加硬件资源。被动反应它现在使用Reactive Streams来允许从调用返回到服务器的数据的异步处理。这意味着我们将看到更少的<code>List</code>s,<code>Collection</code>甚至单个对象,而不是他们的反应等价物,例如<code>Flux</code>和<code>Mono</code>(来自Reactor)。我不会深入研究Reactive Streams是什么,诚实地说,在我尝试向任何人解释它之前,我需要更加深入地研究它。相反,让我们回过头来关注WebFlux。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">像往常一样,我使用Spring Boot在本教程中编写代码。</span></span></span><br /> <span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">以下是我在这篇文章中使用的依赖关系。</span></span></span><br />  </p> <pre> <code class="language-xml"><dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-cassandra-reactive</artifactId> <version>2.0.0.RELEASE</version> </dependency> </dependencies></code></pre> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">尽管我没有将它包含在上面的依赖代码片段中,但是它<code>spring-boot-starter-parent</code>被使用了,最终可以将其提升到版本<code>2.0.0.RELEASE</code>。本教程是关于WebFlux的,包括这<code>spring-boot-starter-webflux</code>显然是一个好主意。<code>spring-boot-starter-data-cassandra-reactive</code>也被包括在内,因为我们将用它作为示例应用程序的数据库,因为它是少数几个有反应支持的数据库之一(在编写本文时)。通过一起使用这些依赖关系,我们的应用程序可以从前到后完全反应。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">WebFlux引入了一种不同的方式来处理请求,而不是使用Spring MVC 中使用的<code>@Controller</code>或<code>@RestController</code>编程模型。但是,它并没有取代它。相反,它已被更新以允许使用被动类型。这使您可以保持与使用Spring编写相同的格式,但对返回类型进行一些更改,以便返回<code>Flux</code>s或<code>Mono</code>s。下面是一个非常人为的例子。</span></span></span><br />  </p> <pre> <code class="language-java">@RestController public class PersonController { private final PersonRepository personRepository; public PersonController(PersonRepository personRepository) { this.personRepository = personRepository; } @GetMapping("/people") public Flux<Person> all() { return personRepository.findAll(); } @GetMapping("/people/{id}") Mono<Person> findById(@PathVariable String id) { return personRepository.findOne(id); } }</code></pre> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">对我来说,这看起来非常熟悉,并且从一眼就可以看出它与标准的Spring MVC控制器没有任何区别,但通过阅读方法后,我们可以看到不同的返回类型。在这个例子中<code>PersonRepository</code>必须是一个被动库,因为我们已经能够直接返回他们的搜索查询的结果供参考,被动库会返回一个<code>Flux</code>集合和一个<code>Mono</code>单一的实体。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">注释方法不是我想在这篇文章中关注的内容。这对我们来说不够酷,时髦。没有足够的lambda表达式来满足我们以更有效的方式编写Java的渴望。但Spring WebFlux有我们的支持。它提供了一种替代方法来路由和处理请求到我们的服务器,轻轻地使用lambdas编写路由器功能。我们来看一个例子。</span></span></span><br />  </p> <pre> <code class="language-java">@Configuration public class PersonRouter { @Bean public RouterFunction<ServerResponse> route(PersonHandler personHandler) { return RouterFunctions.route(GET("/people/{id}").and(accept(APPLICATION_JSON)), personHandler::get) .andRoute(GET("/people").and(accept(APPLICATION_JSON)), personHandler::all) .andRoute(POST("/people").and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::post) .andRoute(PUT("/people/{id}").and(accept(APPLICATION_JSON)).and(contentType(APPLICATION_JSON)), personHandler::put) .andRoute(DELETE("/people/{id}"), personHandler::delete) .andRoute(GET("/people/country/{country}").and(accept(APPLICATION_JSON)), personHandler::getByCountry); } }</code></pre> 这些都是<code>PersonHandler</code>我们稍后会看到的方法的所有路线。我们创建了一个将处理我们路由的bean。为了设置路由功能,我们使用了名为的<code>RouterFunctions</code>类为我们提供了一个静态方法,但现在我们只关心它的<code>route</code>方法。以下是该<code>route</code>方法的签名。 <pre> <code class="language-java">public static <T extends ServerResponse> RouterFunction<T> route( RequestPredicate predicate, HandlerFunction<T> handlerFunction) { // stuff }</code></pre>   <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">该方法表明,它与a <code>RequestPredicate</code>一起<code>HandlerFunction</code>并输出a <code>RouterFunction</code>。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">这<code>RequestPredicate</code>是我们用来指定路由的行为,比如我们处理函数的路径,它是什么类型的请求以及它可以接受的输入类型。由于我使用静态导入将所有内容读得更清晰,所以一些重要信息已经隐藏起来。要创建一个<code>RequestPredicate</code>我们应该使用<code>RequestPredicates</code>(复数),一个静态帮助类为我们提供我们需要的所有方法。就个人而言,我建议静态导入,<code>RequestPredicates</code>否则由于使用<code>RequestPredicates</code>静态方法可能需要的次数,您的代码将会一团糟。在上述例子中,<code>GET</code>,<code>POST</code>,<code>PUT</code>,<code>DELETE</code>,<code>accept</code>和<code>contentType</code>都是静态<code>RequestPredicates</code>方法。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">下一个参数是a <code>HandlerFunction</code>,它是一个功能接口。这里有三件重要的信息,它有一个泛型类型<code><T extends ServerResponse></code>,它的<code>handle</code>方法返回一个<code>Mono<T></code>并且需要一个<code>ServerRequest</code>。使用这些我们可以确定我们需要传递一个返回一个<code>Mono<ServerResponse></code>(或它的一个子类型)的函数。这显然对我们的处理函数返回的内容有严格的约束,因为它们必须满足这个要求,否则它们将不适合以这种格式使用。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">最后的结果是一个<code>RouterFunction</code>。这可以返回并用于路由到我们指定的任何函数。但通常情况下,我们希望一次将很多不同的请求发送给各种处理程序,这是WebFlux迎合的。由于<code>route</code>返回a <code>RouterFunction</code>以及<code>RouterFunction</code>也有其自己的路由方法的事实<code>andRoute</code>,我们可以将这些调用链接在一起并继续添加我们所需的所有额外路由。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">如果我们再回头看一下<code>PersonRouter</code>上面的例子,我们可以看到这些方法是以REST动词命名的,例如<code>GET</code>,<code>POST</code>它们定义了处理程序将要执行的请求的路径和类型。<code>GET</code>例如,如果我们以第一个请求为例,它将<code>/people</code>使用路径变量名称<code>id</code>(path表示的路径变量<code>{id}</code>)和返回内容的类型(具体来说,使用该方法定义的<code>APPLICATION_JSON</code>静态字段from <code>MediaType</code>)进行路由<code>accept</code>。如果使用不同的路径,则不会被处理。如果路径正确但Accept头不是可接受的类型之一,则请求将失败。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">在我们继续之前,我想了解一下<code>accept</code>和<code>contentType</code>方法。这两个设置请求标头都<code>accept</code>与Accept标头和<code>contentType</code>Content-Type 匹配。Accept头定义了响应可接受的媒体类型,因为我们返回的<code>Person</code>对象的JSON表示设置为<code>APPLICATION_JSON</code>(<code>application/json</code>在实际头文件中)是有意义的。Content-Type具有相同的想法,但是却描述了发送请求正文内的媒体类型。这就是为什么只有动词<code>POST</code>和<code>PUT</code>动词才<code>contentType</code>包括在内,因为其他人在他们的身体中没有任何东西。<code>DELETE</code>不包括<code>accept</code>和<code>contentType</code> 所以我们可以得出这样的结论:它既没有期望返回任何东西,也没有在其请求中包含任何东西。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">现在我们知道如何设置路由,让我们看看如何编写处理传入请求的处理程序方法。以下是处理前面示例中定义的路由的所有请求的代码。</span></span></span><br />  </p> <pre> <code class="language-java">@Component public class PersonHandler { private final PersonManager personManager; public PersonHandler(PersonManager personManager) { this.personManager = personManager; } public Mono<ServerResponse> get(ServerRequest request) { final UUID id = UUID.fromString(request.pathVariable("id")); final Mono<Person> person = personManager.findById(id); return person .flatMap(p -> ok().contentType(APPLICATION_JSON).body(fromPublisher(person, Person.class))) .switchIfEmpty(notFound().build()); } public Mono<ServerResponse> all(ServerRequest request) { return ok().contentType(APPLICATION_JSON) .body(fromPublisher(personManager.findAll(), Person.class)); } public Mono<ServerResponse> put(ServerRequest request) { final UUID id = UUID.fromString(request.pathVariable("id")); final Mono<Person> person = request.bodyToMono(Person.class); return personManager .findById(id) .flatMap( old -> ok().contentType(APPLICATION_JSON) .body( fromPublisher( person .map(p -> new Person(p, id)) .flatMap(p -> personManager.update(old, p)), Person.class))) .switchIfEmpty(notFound().build()); } public Mono<ServerResponse> post(ServerRequest request) { final Mono<Person> person = request.bodyToMono(Person.class); final UUID id = UUID.randomUUID(); return created(UriComponentsBuilder.fromPath("people/" + id).build().toUri()) .contentType(APPLICATION_JSON) .body( fromPublisher( person.map(p -> new Person(p, id)).flatMap(personManager::save), Person.class)); } public Mono<ServerResponse> delete(ServerRequest request) { final UUID id = UUID.fromString(request.pathVariable("id")); return personManager .findById(id) .flatMap(p -> noContent().build(personManager.delete(p))) .switchIfEmpty(notFound().build()); } public Mono<ServerResponse> getByCountry(ServerRequest serverRequest) { final String country = serverRequest.pathVariable("country"); return ok().contentType(APPLICATION_JSON) .body(fromPublisher(personManager.findAllByCountry(country), Person.class)); } }</code></pre>   <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">有一点非常明显,就是缺少注释。酒吧的<code>@Component</code>注释自动创建一个<code>PersonHandler</code>豆没有其他Spring注解。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">我试图将大部分存储库逻辑保留在这个类之外,并且通过经由它所包含的<code>PersonManager</code>代理来隐藏对实体对象的任何引用<code>PersonRepository</code>。如果你对代码感兴趣,<code>PersonManager</code>那么可以在我的<a href="https://github.com/lankydan/spring-boot-webflux/blob/master/src/main/java/com/lankydanblog/tutorial/person/PersonManager.java" rel="external nofollow" style="padding:0px; margin:0px; outline:none; list-style:none; border:0px none; color:#326693; text-decoration:none; transition:all 0.2s ease-in-out" target="_blank" >GitHub上</a>看到,关于它的进一步解释将被排除在这篇文章之外,所以我们可以专注于WebFlux本身。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">好的,回到手头的代码。让我们仔细看看<code>get</code>和<code>post</code>方法来弄清楚发生了什么。</span></span></span><br />  </p> <pre> <code class="language-java">public Mono<ServerResponse> get(ServerRequest request) { final UUID id = UUID.fromString(request.pathVariable("id")); final Mono<Person> person = personManager.findById(id); return person .flatMap(p -> ok().contentType(APPLICATION_JSON).body(fromPublisher(person, Person.class))) .switchIfEmpty(notFound().build()); }</code></pre> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">此方法用于从支持此示例应用程序的数据库中检索单个记录。由于Cassandra是选择的数据库,我决定使用<code>UUID</code>每个记录的主键,这使得测试示例更令人讨厌的不幸效果,但没有任何复制和粘贴无法解决的问题。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">请记住,此<code>GET</code>请求的路径中包含路径变量。使用<code>pathVariable</code>的方法<code>ServerRequest</code>传递到我们能够提取它的价值通过提供变量的名称,在这种情况下,方法<code>id</code>。然后将ID转换成一个<code>UUID</code>,如果字符串格式不正确,它会抛出一个异常,我决定忽略这个问题,所以示例代码不会变得混乱。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">一旦我们有了ID,我们就可以查询数据库中是否存在匹配的记录。<code>Mono<Person></code>返回的A 包含映射到a的现有记录,<code>Person</code>或者它保留为空<code>Mono</code>。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">使用返回的,<code>Mono</code>我们可以根据它的存在输出不同的响应。这意味着我们可以将有用的状态代码返回给客户端以跟随主体的内容。如果记录存在,则<code>flatMap</code>返回一个<code>ServerResponse</code>与<code>OK</code>状态。伴随着这种状态,我们希望输出记录,为此,我们在这种情况下指定正文的内容类型<code>APPLICATION_JSON</code>,并将记录添加到记录中。<code>fromPublisher</code>需要我们<code>Mono<Person></code>(这是一个<code>Publisher</code>)与<code>Person</code>课程一起,因此它知道它映射到身体中的是什么。<code>fromPublisher</code>是类的静态方法<code>BodyInserters</code>。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">如果记录不存在,那么流程将移动到<code>switchIfEmpty</code>块中并返回<code>NOT FOUND</code>状态。因为没有发现,身体可以留空,所以我们只是创建<code>ServerResponse</code>那里。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">现在到<code>post</code>处理程序。</span></span></span><br />  </p> <pre> <code class="language-java">public Mono<ServerResponse> post(ServerRequest request) { final Mono<Person> person = request.bodyToMono(Person.class); final UUID id = UUID.randomUUID(); return created(UriComponentsBuilder.fromPath("people/" + id).build().toUri()) .contentType(APPLICATION_JSON) .body( fromPublisher( person.map(p -> new Person(p, id)).flatMap(personManager::save), Person.class)); }</code></pre>   <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">即使只是从第一行开始,我们就可以看到,这种<code>get</code>方法的工作方式已经不同了。由于这是一个<code>POST</code>请求,它需要接受我们希望从请求主体持续存在的对象。由于我们试图插入单个记录,因此我们将使用请求的<code>bodyToMono</code>方法<code>Person</code>从正文中检索。如果您正在处理多个记录,则可能需要使用它们<code>bodyToFlux</code>。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">我们将<code>CREATED</code>使用<code>created</code>接受a 的方法返回状态<code>URI</code>以确定插入记录的路径。然后,<code>get</code>通过使用该<code>fromPublisher</code>方法将新记录添加到响应主体,然后采用与该方法类似的设置。形成该代码的代码<code>Publisher</code>稍有不同,但输出仍然<code>Mono<Person></code>是一个重要的内容。为了进一步解释如何完成插入<code>Person</code>,从请求传入的内容将被映射到<code>Person</code>使用<code>UUID</code>我们生成的新内容,然后通过<code>save</code>调用传递给新内容<code>flatMap</code>。通过创建一个新的<code>Person</code>我们只将值插入我们允许的Cassandra中,在这种情况下,我们不希望<code>UUID</code>从请求体传入。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">所以说,这是关于处理程序。显然还有其他方法,我们没有经历。它们的工作方式都不相同,但都遵循相同的概念,<code>ServerResponse</code>如果需要,它返回一个包含适当状态代码和记录的体系。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">现在我们已经编写了所有我们需要的代码来获得基本的Spring WebFlux后端运行。剩下的就是将所有配置绑定在一起,这对Spring Boot来说很简单。</span></span></span></p> <pre> <code class="language-java">@SpringBootApplication public class Application { public static void main(String args[]) { SpringApplication.run(Application.class); } }</code></pre>   <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">我们应该研究如何真正使用代码,而不是结束这篇文章。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">Spring提供了<code>WebClient</code>该类来处理请求而不会阻塞。我们现在可以利用这个来测试应用程序,尽管<code>WebTestClient</code>我们也可以在这里使用它。该<code>WebClient</code>是你可以使用,而不是阻止什么<code>RestTemplate</code>产生反应的应用程序时。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">下面是一些调用在<code>PersonHandler</code>。中定义的处理程序的代码。</span></span></span><br />  </p> <pre> <code class="language-java">public class Client { private WebClient client = WebClient.create("http://localhost:8080"); public void doStuff() { // POST final Person record = new Person(UUID.randomUUID(), "John", "Doe", "UK", 50); final Mono<ClientResponse> postResponse = client .post() .uri("/people") .body(Mono.just(record), Person.class) .accept(APPLICATION_JSON) .exchange(); postResponse .map(ClientResponse::statusCode) .subscribe(status -> System.out.println("POST: " + status.getReasonPhrase())); // GET client .get() .uri("/people/{id}", "a4f66fe5-7c1b-4bcf-89b4-93d8fcbc52a4") .accept(APPLICATION_JSON) .exchange() .flatMap(response -> response.bodyToMono(Person.class)) .subscribe(person -> System.out.println("GET: " + person)); // ALL client .get() .uri("/people") .accept(APPLICATION_JSON) .exchange() .flatMapMany(response -> response.bodyToFlux(Person.class)) .subscribe(person -> System.out.println("ALL: " + person)); // PUT final Person updated = new Person(UUID.randomUUID(), "Peter", "Parker", "US", 18); client .put() .uri("/people/{id}", "ec2212fc-669e-42ff-9c51-69782679c9fc") .body(Mono.just(updated), Person.class) .accept(APPLICATION_JSON) .exchange() .map(ClientResponse::statusCode) .subscribe(response -> System.out.println("PUT: " + response.getReasonPhrase())); // DELETE client .delete() .uri("/people/{id}", "ec2212fc-669e-42ff-9c51-69782679c9fc") .exchange() .map(ClientResponse::statusCode) .subscribe(status -> System.out.println("DELETE: " + status)); } }</code></pre> 不要忘了在<code>Client</code>某个地方实例化,下面是一个很好的偷懒方式来做到这一点! <pre> <code class="language-java">@SpringBootApplication public class Application { public static void main(String args[]) { SpringApplication.run(Application.class); Client client = new Client(); client.doStuff(); } }</code></pre> 首先我们创建一个<code>WebClient</code>。 <pre> <code class="language-java">private final WebClient client = WebClient.create("http://localhost:8080"); </code></pre> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">一旦创建,我们就可以开始做它的东西,因此<code>doStuff</code>方法。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">我们来分解一下<code>POST</code>发送给后端的请求。</span></span></span><br />  </p> <pre> <code class="language-java">final Mono<ClientResponse> postResponse = client .post() .uri("/people") .body(Mono.just(record), Person.class) .accept(APPLICATION_JSON) .exchange(); postResponse .map(ClientResponse::statusCode) .subscribe(status -> System.out.println("POST: " + status.getReasonPhrase()));</code></pre> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">我写下这个稍有不同,所以你可以看到a <code>Mono<ClientResponse></code>是从发送请求返回的。该<code>exchange</code>方法将HTTP请求发送到服务器。然后,只要响应到达,就会处理响应,如果有的话。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">使用<code>WebClient</code>我们指定我们想要<code>POST</code>使用<code>post</code>当然的方法发送请求。在<code>URI</code>随后与所添加的<code>uri</code>方法(重载的方法,这一个接受一个<code>String</code>但另一个接受<code>URI</code>)。我厌倦了说这个方法做了什么方法,所以,身体的内容随后与Accept头一起添加。最后我们通过电话发送请求<code>exchange</code>。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">请注意,媒体类型<code>APPLICATION_JSON</code>与<code>POST</code>路由器功能中定义的类型相匹配。如果我们要发送不同的类型,比如说<code>TEXT_PLAIN</code>我们会得到一个<code>404</code>错误,因为没有处理程序存在与请求期望返回的内容相匹配的地方。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">使用<code>Mono<ClientResponse></code>通过调用返回<code>exchange</code>,我们可以绘制它的内容给我们所需的输出。在上面的例子中,状态代码被打印到控制台。如果我们回想一下<code>post</code>方法<code>PersonHandler</code>,请记住它只能返回“创建”状态,但如果发送的请求没有正确匹配,则会打印出“未找到”。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">我们来看看其他请求之一。</span></span></span><br />  </p> <pre> <code class="language-java">client .get() .uri("/people/{id}", "a4f66fe5-7c1b-4bcf-89b4-93d8fcbc52a4") .accept(APPLICATION_JSON) .exchange() .flatMap(response -> response.bodyToMono(Person.class)) .subscribe(person -> System.out.println("GET: " + person));</code></pre> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">这是我们的典型<code>GET</code>要求。它看起来与<code>POST</code>我们刚刚经历的请求非常相似。主要区别在于<code>uri</code>,请求路径和<code>UUID</code>(作为<code>String</code>在这种情况下)作为参数来取代路径变量<code>{id}</code>并且主体留空。响应如何处理也是不同的。在这个例子中,它提取了响应的主体并将其映射到a <code>Mono<Person></code>并打印出来。这可以在前面的<code>POST</code>例子中完成,但是响应的状态代码对于它的情况更有用。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">对于略有不同的观点,我们可以使用cURL发出请求并查看响应的样子。</span></span></span><br />  </p> <pre> <code class="language-html">CURL -H "Accept:application/json" -i localhost:8080/people HTTP/1.1 200 OK transfer-encoding: chunked Content-Type: application/json [ { "id": "13c403a2-6770-4174-8b76-7ba7b75ef73d", "firstName": "John", "lastName": "Doe", "country": "UK", "age": 50 }, { "id": "fbd53e55-7313-4759-ad74-6fc1c5df0986", "firstName": "Peter", "lastName": "Parker", "country": "US", "age": 50 } ] </code></pre> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">响应看起来像这样,显然它会根据您存储的数据而有所不同。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">请注意响应标题。</span></span></span><br />  </p> <pre> <code class="language-html">transfer-encoding: chunked Content-Type: application/json</code></pre> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">    在<code>transfer-encoding</code>这里表示的是在可用于流式传输的数据块传输的数据。这就是我们需要的,因此客户可以对返回给它的数据采取反应态度。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">我认为这应该是一个停止的好地方。我们在这里已经涵盖了相当多的材料,希望能够帮助您更好地理解Spring WebFlux。还有一些其他的话题我想关注WebFlux,但是我会在单独的帖子中做这些,因为我认为这个主题足够长。</span></span></span></p> <p style="text-align:start"><span style="color:#333333"><span style="font-family:Tahoma,Arial,Verdana,sans-serif"><span style="background-color:#ffffff">    总之,在这篇文章中,我们非常简要地讨论了为什么你想在典型的Spring MVC后端中使用Spring WebFlux。然后我们看看如何设置路由和处理程序来处理传入的请求。处理程序实现了可以处理大多数REST动词的方法,并在响应中返回了正确的数据和状态代码。最后,我们研究了向后端发送请求的两种方式,一种是使用a <code>WebClient</code>直接在客户端处理输出,另一种使用cURL查看返回的JSON的外观。</span></span></span></p> <br /> <span style="color:#ff0000"><strong>注意源码中使用的数据为:cassandra</strong></span><br /> <br /> <a href="http://www.leftso.com/resource/1004.html" target="_blank" ><strong>项目源码下载</strong></a>
  • Spring WebFlux 和Reactive MongoDB来构建Reactive Rest API

    Spring WebFlux 和Reactive MongoDB来构建Reactive Rest API,Spring 5通过引入一种名为Spring WebFlux的全新响应式框架来支持反应式编程范例。<h2>1.引言</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">Spring 5通过引入一种名为<strong>Spring WebFlux的</strong>全新反应框架来支持响应式编程范例。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">Spring WebFlux是一个自下而上的异步框架。它可以使用Servlet 3.1非阻塞IO API以及其他异步运行时环境(如netty或undertow)在Servlet容器上运行。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">它可以与Spring MVC一起使用。是的,Spring MVC不会去任何地方。这是一个开发人员长期以来使用的流行的Web框架。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">但是你现在可以在新的反应框架和传统的Spring MVC之间做出选择。您可以根据自己的使用情况选择使用它们中的任何一个。</span></span></span><br /> <br />  </p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">Spring WebFlux使用一个名为Reactor的库作为响应支持。Reactor是<a href="https://github.com/reactive-streams/reactive-streams-jvm#reactive-streams" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank" >Reactive Streams</a>规范的一个实现。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">Reactor提供两种主要的类型,称为<code>Flux</code>和<code>Mono</code>。这两种类型都实现了<code>Publisher</code>Reactive Streams提供的接口。<code>Flux</code>用于表示0..N个元素的流,<code>Mono</code>用于表示0..1个元素的流。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">虽然Spring使用Reactor作为其大部分内部API的核心依赖,但它也支持在应用程序级别使用RxJava。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"> </p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">2.Spring WebFlux支持的编程模型</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">Spring WebFlux支持两种类型的编程模型:</span></span></span></p> <ol> <li>带有<code>@Controller</code>,<code>@RequestMapping</code>和其他注释的基于注释的传统模型,您在Spring MVC中一直使用。</li> <li>基于Java 8 lambda表达式的全新功能样式模型,用于路由和处理请求。</li> </ol> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">在本文中,我们将使用传统的基于注释的编程模型。我将在未来的文章中撰写功能风格模型。</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start"><br /> 3.让我们在Spring Boot中构建一个Reactive Restful服务</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">在本文中,我们将为迷你Twitter应用程序构建一个Restful API。该应用程序将只有一个称为的域模型<code>Tweet</code>。每个<code>Tweet</code>人都有<code>text</code>一个<code>createdAt</code>领域。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">我们将使用MongoDB作为我们的数据存储以及反应型mongodb驱动程序。我们将构建用于创建,检索,更新和删除Tweet的REST API。所有的REST API都是异步的,并且会返回一个发布者。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">我们还将学习如何将数据从数据库传输到客户端。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">最后,我们将编写集成测试以使用Spring 5提供的新异步WebTestClient测试所有API。</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">4.创建项目</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">我们使用Spring Initializr Web应用程序来生成我们的应用程序。按照以下步骤生成项目 -</span></span></span></p> <ol> <li>转到<a href="http://start.spring.io/" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank" >http://start.spring.io</a></li> <li>选择Spring Boot版本<strong>2.x</strong></li> <li>输入工件的值作为<strong>webflux-demo</strong></li> <li>添加<strong>Reactive Web</strong>和<strong>Reactive MongoDB</strong>依赖项</li> <li>点击<strong>生成项目</strong>生成并下载项目。</li> </ol> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><img srcset="" width="" size="" class="img-thumbnail" alt="spring官网生成项目" src="/resources/assist/images/blog/bc7bae06bc4c4868a7df2ed2f49828c8.png" /></span></span></span><br /> 下载项目后,将其解压缩并导入到您最喜欢的IDE中。该项目的目录结构应该如下所示 -<br /> <span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><img srcset="" width="" size="" class="img-thumbnail" alt="导入spring 官网生成的maven项目" src="/resources/assist/images/blog/94b74bcde26549c2a0f52a246f54b2e5.png" /></span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">配置MongoDB</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">您可以通过简单地将以下属性添加到<code>application.properties</code>文件来配置MongoDB -</span></span></span></p> <pre> <code class="language-html">spring.data.mongodb.uri=mongodb://localhost:27017/webflux_demo</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">Spring Boot将在启动时读取此配置并自动配置数据源。</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">创建领域模型</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">让我们创建我们的领域模型 - <code>Tweet</code>。创建一个名为<code>model</code>inside <code>com.example.webfluxdemo</code>package 的新包,然后创建一个名为<code>Tweet.java</code>以下内容的文件-</span></span></span></p> <pre> <code class="language-java">import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import java.util.Date; @Document(collection = "tweets") public class Tweet { @Id private String id; @NotBlank @Size(max = 140) private String text; @NotNull private Date createdAt = new Date(); public Tweet() { } public Tweet(String text) { this.id = id; this.text = text; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getText() { return text; } public void setText(String text) { this.text = text; } public Date getCreatedAt() { return createdAt; } public void setCreatedAt(Date createdAt) { this.createdAt = createdAt; } }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start">够简单!Tweet模型包含一个<code>text</code>和一个<code>createdAt</code>字段。该<code>text</code>字段用注释<code>@NotBlank</code>和<code>@Size</code>注释确保它不是空白并且最多有140个字符。<br /> <br />  </p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">5.创建存储库</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">接下来,我们将创建将用于访问MongoDB数据库的数据访问层。创建一个名为<code>repository</code>inside 的新包<code>com.example.webfluxdemo</code>,然后<code>TweetRepository.java</code>使用以下内容创建一个新文件-</span></span></span></p> <pre> <code class="language-java">import com.example.webfluxdemo.model.Tweet; import org.springframework.data.mongodb.repository.ReactiveMongoRepository; import org.springframework.stereotype.Repository; @Repository public interface TweetRepository extends ReactiveMongoRepository<Tweet, String> { }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">该<code>TweetRepository</code>接口扩展<a href="https://docs.spring.io/spring-data/mongodb/docs/2.0.0.RC2/api/org/springframework/data/mongodb/repository/ReactiveMongoRepository.html" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank" ><code>ReactiveMongoRepository</code></a>了文档中的各种CRUD方法。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">Spring Boot自动插入在<a href="https://docs.spring.io/spring-data/mongodb/docs/2.0.0.RC2/api/org/springframework/data/mongodb/repository/support/SimpleReactiveMongoRepository.html" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank" ><code>SimpleReactiveMongoRepository</code></a>运行时调用的此接口的实现。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">因此,您无需编写任何代码就可以轻松获取文档上的所有CRUD方法。以下是一些可用的方法<code>SimpleReactiveMongoRepository</code>-</span></span></span></p> <pre> <code class="language-java">reactor.core.publisher.Flux<T> findAll(); reactor.core.publisher.Mono<T> findById(ID id); <S extends T> reactor.core.publisher.Mono<S> save(S entity); reactor.core.publisher.Mono<Void> delete(T entity);</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">请注意,所有方法都是异步的,并以a <code>Flux</code>或<code>Mono</code>类型的形式返回发布者。</span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">创建控制器端点</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">最后,让我们编写将暴露给客户端的API。创建一个名为<code>controller</code>inside 的新包<code>com.example.webfluxdemo</code>,然后<code>TweetController.java</code>使用以下内容创建一个新文件-</span></span></span><br />  </p> <pre> <code class="language-java">import com.example.webfluxdemo.model.Tweet; import com.example.webfluxdemo.repository.TweetRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import javax.validation.Valid; @RestController public class TweetController { @Autowired private TweetRepository tweetRepository; @GetMapping("/tweets") public Flux<Tweet> getAllTweets() { return tweetRepository.findAll(); } @PostMapping("/tweets") public Mono<Tweet> createTweets(@Valid @RequestBody Tweet tweet) { return tweetRepository.save(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()); } @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)); } @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)); } // Tweets are Sent to the client as Server Sent Events @GetMapping(value = "/stream/tweets", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<Tweet> streamAllTweets() { return tweetRepository.findAll(); } }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start">所有的控制器端点都以Flux或Mono的形式返回一个Publisher。我们将内容类型设置为的最后一个端点非常有趣<code>text/event-stream</code>。它以<strong><a href="https://en.wikipedia.org/wiki/Server-sent_events" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank" >服务器发送事件</a></strong>的形式将推文<strong><a href="https://en.wikipedia.org/wiki/Server-sent_events" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank" >发送</a></strong>到像这样的浏览器 -</p> <pre> <code class="language-json">data: {"id":"59ba5389d2b2a85ed4ebdafa","text":"tweet1","createdAt":1505383305602} data: {"id":"59ba5587d2b2a85f93b8ece7","text":"tweet2","createdAt":1505383814847}</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start">现在我们正在讨论事件流,您可能会问以下端点是否也返回一个Stream?</p> <pre> <code class="language-java">@GetMapping("/tweets") public Flux<Tweet> getAllTweets() { return tweetRepository.findAll(); }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">答案是肯定的。<code>Flux<Tweet></code>代表推文流。但是,默认情况下,它将生成一个JSON数组,因为如果将单个JSON对象流发送给浏览器,那么它将不会是一个有效的JSON文档。除了使用Server-Sent-Events或WebSocket之外,浏览器客户端无法使用流。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">但是,非浏览器客户端可以通过设置<code>Accept</code>标头来请求JSON流<code>application/stream+json</code>,并且响应将是类似于Server-Sent-Events的JSON流,但不需要额外的格式:</span></span></span></p> <pre> <code class="language-json">{"id":"59ba5389d2b2a85ed4ebdafa","text":"tweet1","createdAt":1505383305602} {"id":"59ba5587d2b2a85f93b8ece7","text":"tweet2","createdAt":1505383814847}</code></pre> <h2 style="margin-left:0px; margin-right:0px; text-align:start">使用WebTestClient进行集成测试</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">Spring 5还提供了一个异步和被动的http客户端,<code>WebClient</code>用于处理异步和流式API。这是一个被动的选择<code>RestTemplate</code>。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">此外,你还可以得到一个<code>WebTestClient</code>写作集成测试。测试客户端可以运行在实时服务器上,也可以用于模拟请求和响应。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">我们将使用WebTestClient为我们的REST API编写集成测试。打开<code>WebfluxDemoApplicationTests.java</code>文件并将以下测试添加到它 -</span></span></span></p> <pre> <code class="language-java">import com.example.webfluxdemo.model.Tweet; import com.example.webfluxdemo.repository.TweetRepository; import org.assertj.core.api.Assertions; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.reactive.server.WebTestClient; import reactor.core.publisher.Mono; import java.util.Collections; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class WebfluxDemoApplicationTests { @Autowired private WebTestClient webTestClient; @Autowired TweetRepository tweetRepository; @Test public void testCreateTweet() { Tweet tweet = new Tweet("This is a Test 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("This is a Test Tweet"); } @Test public void testGetAllTweets() { webTestClient.get().uri("/tweets") .accept(MediaType.APPLICATION_JSON_UTF8) .exchange() .expectStatus().isOk() .expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8) .expectBodyList(Tweet.class); } @Test public void testGetSingleTweet() { Tweet tweet = tweetRepository.save(new Tweet("Hello, World!")).block(); webTestClient.get() .uri("/tweets/{id}", Collections.singletonMap("id", tweet.getId())) .exchange() .expectStatus().isOk() .expectBody() .consumeWith(response -> Assertions.assertThat(response.getResponseBody()).isNotNull()); } @Test public void testUpdateTweet() { Tweet tweet = tweetRepository.save(new Tweet("Initial Tweet")).block(); Tweet newTweetData = new Tweet("Updated Tweet"); webTestClient.put() .uri("/tweets/{id}", Collections.singletonMap("id", tweet.getId())) .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8) .body(Mono.just(newTweetData), Tweet.class) .exchange() .expectStatus().isOk() .expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8) .expectBody() .jsonPath("$.text").isEqualTo("Updated Tweet"); } @Test public void testDeleteTweet() { Tweet tweet = tweetRepository.save(new Tweet("To be deleted")).block(); webTestClient.delete() .uri("/tweets/{id}", Collections.singletonMap("id", tweet.getId())) .exchange() .expectStatus().isOk(); } }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start">在上面的例子中,我为所有的CRUD API编写了测试。您可以通过转到项目的根目录并键入来运行测试<code>mvn test</code>。<br />  </p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">6.总结</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">在本文中,我们学习了使用Spring进行反应式编程的基础知识,并使用Spring WebFlux框架提供的反应式支持构建了一个简单的Restful服务。我们还使用WebTestClient测试了所有Rest API。</span></span></span></p> <blockquote> <p style="margin-left:40px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">提示:<a href="http://www.leftso.com/resource/1012.html" target="_blank" >项目源码下载</a></span></span></span></p> </blockquote>
  • Spring 5 WebClient和WebTestClient使用教程

    Spring 5 WebClient和WebTestClient使用教程,Spring开发人员,您是否曾经觉得需要一个易于使用且高效的流畅功能样式 API 的异步/非阻塞 HTTP客户端? 如果是,那么我欢迎您阅读关于WebClient的文章,WebClient是Spring 5中引入的新的被动HTTP客户端。<h2 style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">1.引言</span></span></span></h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">Spring开发人员,您是否曾经觉得需要一个易于使用且高效的<strong>流畅功能样式</strong> API 的<strong>异步/非阻塞</strong> HTTP客户端?</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">如果是,那么我欢迎您阅读关于WebClient的文章,WebClient是Spring 5中引入的新的被动HTTP客户端。</span></span></span><br /> <br />  </p> <h2 style="margin-left:0px; margin-right:0px; text-align:start">2.如何使用WebClient</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">WebClient是Spring 5的反应性Web框架Spring WebFlux的一部分。要使用WebClient,您需要将<code>spring-webflux</code>模块包含在您的项目中。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong>在现有的Spring Boot项目中添加依赖项</strong></span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">如果您有一个现有的Spring Boot项目,则可以<code>spring-webflux</code>通过在该<code>pom.xml</code>文件中添加以下依赖项来添加该模块-</span></span></span></p> <pre> <code class="language-xml"><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency></code></pre> <blockquote> <p style="margin-left:0px; margin-right:0px; text-align:start">请注意,您需要Spring Boot 2.xx版本才能使用Spring WebFlux模块。</p> </blockquote> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong>从Scratch创建一个新项目</strong></span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">如果您从头开始创建项目,那么您可以使用<a href="http://start.spring.io/" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank" >Spring Initializr</a>网站的<code>spring-webflux</code>模块生成初始项目-</span></span></span></p> <ol> <li>转到<a href="http://start.spring.io/" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank" >http://start.spring.io</a>。</li> <li>选择弹簧引导版本<strong>2.xx的</strong>。</li> <li>在依赖项部分添加<strong>反应性Web</strong>依赖项。</li> <li>如果需要,请更改<em>组</em>和<em>工件的</em>详细信息,然后单击生成工程下载项目。</li> </ol> <h2 style="margin-left:0px; margin-right:0px; text-align:start">3.使用WebClient消费远程API</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><em>让我们做一些有趣的事情,并使用WebClient来使用Real World API。</em></span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">在本文中,我们将使用WebClient来使用<a href="https://developer.github.com/v3/" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank" >Github的API</a>。我们将使用WebClient在用户的Github存储库上执行CRUD操作。</span></span></span></p> <h3 style="margin-left:0px; margin-right:0px; text-align:start">创建WebClient的一个实例</h3> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong>1.使用该<code>create()</code>方法创建WebClient</strong></span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">您可以使用<code>create()</code>工厂方法创建WebClient的实例-</span></span></span></p> <pre> <code class="language-java">WebClient webClient = WebClient.create();</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start">如果您只使用特定服务的API,那么您可以使用该服务的baseUrl来初始化WebClient</p> <pre> <code class="language-java">WebClient webClient = WebClient.create("https://api.github.com");</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><strong>2.使用WebClient构建器创建WebClient</strong></span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">WebClient还附带了一个构建器,它为您提供了一些自定义选项,包括过滤器,默认标题,cookie,客户端连接器等 -</span></span></span></p> <pre> <code class="language-java">WebClient webClient = WebClient.builder() .baseUrl("https://api.github.com") .defaultHeader(HttpHeaders.CONTENT_TYPE, "application/vnd.github.v3+json") .defaultHeader(HttpHeaders.USER_AGENT, "Spring 5 WebClient") .build();</code></pre> <h3 style="margin-left:0px; margin-right:0px; text-align:start">使用WebClient发出请求并检索响应</h3> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">以下是如何使用WebClient <code>GET</code>向<a href="https://developer.github.com/v3/repos/#list-your-repositories" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank" >Github的List Repositories API</a>发出请求-</span></span></span></p> <pre> <code class="language-java">public Flux<GithubRepo> listGithubRepositories(String username, String token) { return webClient.get() .uri("/user/repos") .header("Authorization", "Basic " + Base64Utils .encodeToString((username + ":" + token).getBytes(UTF_8))) .retrieve() .bodyToFlux(GithubRepo.class); }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">了解API调用的简单性和简洁性!</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">假设我们有一个名为类<code>GithubRepo</code>,确认到GitHub的API响应,上面的函数会返回一个<code>Flux</code>的<code>GithubRepo</code>对象。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">请注意,我使用<a href="https://developer.github.com/v3/auth/#via-oauth-tokens" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank" >Github的基本认证</a>机制来调用API。它需要您的github用户名和个人访问令牌,您可以从<a href="https://github.com/settings/tokens" rel="external nofollow" style="box-sizing:border-box; color:#419be8; text-decoration:none; word-wrap:break-word" target="_blank" >https://github.com/settings/tokens中</a>生成该令牌。</span></span></span></p> <h4 style="margin-left:0px; margin-right:0px; text-align:start">使用exchange()方法来检索响应</h4> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">该<code>retrieve()</code>方法是获取响应主体的最简单方法。但是,如果您希望对响应拥有更多的控制权,那么您可以使用可<code>exchange()</code>访问整个<code>ClientResponse</code>标题和正文的方法 -</span></span></span></p> <pre> <code class="language-java">public Flux<GithubRepo> listGithubRepositories(String username, String token) { return webClient.get() .uri("/user/repos") .header("Authorization", "Basic " + Base64Utils .encodeToString((username + ":" + token).getBytes(UTF_8))) .exchange() .flatMapMany(clientResponse -> clientResponse.bodyToFlux(GithubRepo.class)); }</code></pre> <h4 style="margin-left:0px; margin-right:0px; text-align:start">在请求URI中使用参数</h4> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">您可以在请求URI中使用参数,并在<code>uri()</code>函数中分别传递它们的值。所有参数都被花括号包围。在提出请求之前,这些参数将被WebClient自动替换 -</span></span></span></p> <pre> <code class="language-java">public Flux<GithubRepo> listGithubRepositories(String username, String token) { return webClient.get() .uri("/user/repos?sort={sortField}&direction={sortDirection}", "updated", "desc") .header("Authorization", "Basic " + Base64Utils .encodeToString((username + ":" + token).getBytes(UTF_8))) .retrieve() .bodyToFlux(GithubRepo.class); }</code></pre> <h4 style="margin-left:0px; margin-right:0px; text-align:start">使用URIBuilder构造请求URI</h4> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">您也可以使用<code>UriBuilder</code>类似的方法获取对请求URI的完全程序控制,</span></span></span></p> <pre> <code class="language-java">public Flux<GithubRepo> listGithubRepositories(String username, String token) { return webClient.get() .uri(uriBuilder -> uriBuilder.path("/user/repos") .queryParam("sort", "updated") .queryParam("direction", "desc") .build()) .header("Authorization", "Basic " + Base64Utils .encodeToString((username + ":" + token).getBytes(UTF_8))) .retrieve() .bodyToFlux(GithubRepo.class); }</code></pre> <h3 style="margin-left:0px; margin-right:0px; text-align:start">在WebClient请求中传递Request Body</h3> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">如果你有一个<code>Mono</code>或一个形式的请求体<code>Flux</code>,那么你可以直接将它传递给<code>body()</code>WebClient中的方法,否则你可以从一个对象中创建一个单声道/通量并像这样传递 -</span></span></span></p> <pre> <code class="language-java">public Mono<GithubRepo> createGithubRepository(String username, String token, RepoRequest createRepoRequest) { return webClient.post() .uri("/user/repos") .body(Mono.just(createRepoRequest), RepoRequest.class) .header("Authorization", "Basic " + Base64Utils .encodeToString((username + ":" + token).getBytes(UTF_8))) .retrieve() .bodyToMono(GithubRepo.class); }</code></pre> 如果您具有实际值而不是<code>Publisher</code>(<code>Flux</code>/ <code>Mono</code>),则可以使用<code>syncBody()</code>快捷方式传递请求正文 - <pre> <code class="language-java">public Mono<GithubRepo> createGithubRepository(String username, String token, RepoRequest createRepoRequest) { return webClient.post() .uri("/user/repos") .syncBody(createRepoRequest) .header("Authorization", "Basic " + Base64Utils .encodeToString((username + ":" + token).getBytes(UTF_8))) .retrieve() .bodyToMono(GithubRepo.class); }</code></pre> 最后,你可以使用<code>BodyInserters</code>类提供的各种工厂方法来构造一个<code>BodyInserter</code>对象并将其传递给该<code>body()</code>方法。本<code>BodyInserters</code>类包含的方法来创建一个<code>BodyInserter</code>从<code>Object</code>,<code>Publisher</code>,<code>Resource</code>,<code>FormData</code>,<code>MultipartData</code>等- <pre> <code class="language-java">public Mono<GithubRepo> createGithubRepository(String username, String token, RepoRequest createRepoRequest) { return webClient.post() .uri("/user/repos") .body(BodyInserters.fromObject(createRepoRequest)) .header("Authorization", "Basic " + Base64Utils .encodeToString((username + ":" + token).getBytes(UTF_8))) .retrieve() .bodyToMono(GithubRepo.class); }</code></pre> <h3 style="margin-left:0px; margin-right:0px; text-align:start">添加过滤器功能</h3> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">WebClient支持使用<code>ExchangeFilterFunction</code>。您可以使用过滤器函数以任何方式拦截和修改请求。例如,您可以使用过滤器函数为<code>Authorization</code>每个请求添加一个标头,或记录每个请求的详细信息。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">这<code>ExchangeFilterFunction</code>需要两个参数 -</span></span></span></p> <ol style="margin-left:30px; margin-right:0px"> <li>在<code>ClientRequest</code>与</li> <li><code>ExchangeFilterFunction</code>过滤器链中的下一个。</li> </ol> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">它可以修改<code>ClientRequest</code>并调用<code>ExchangeFilterFucntion</code>过滤器链中的下一个来继续下一个过滤器或<code>ClientRequest</code>直接返回修改以阻止过滤器链。</span></span></span></p> <h4 style="margin-left:0px; margin-right:0px; text-align:start">1.使用过滤器功能添加基本认证</h4> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">在上面的所有示例中,我们都包含一个<code>Authorization</code>用于使用Github API进行基本身份验证的标头。由于这是所有请求共有的内容,因此您可以在创建过滤器函数时将此逻辑添加到过滤器函数中<code>WebClient</code>。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">该<code>ExchaneFilterFunctions</code>API已经为基本认证提供了一个过滤器。你可以像这样使用它 -</span></span></span></p> <pre> <code class="language-java">WebClient webClient = WebClient.builder() .baseUrl(GITHUB_API_BASE_URL) .defaultHeader(HttpHeaders.CONTENT_TYPE, GITHUB_V3_MIME_TYPE) .filter(ExchangeFilterFunctions .basicAuthentication(username, token)) .build();</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">现在,您不需要<code>Authorization</code>在每个请求中添加标题。过滤器函数将拦截每个WebClient请求添加此标头。</span></span></span></p> <h4 style="margin-left:0px; margin-right:0px; text-align:start">2.使用过滤器功能记录所有请求</h4> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">我们来看一个习惯的例子<code>ExchangeFilterFunction</code>。我们将编写一个过滤器函数来拦截并记录每个请求 -</span></span></span></p> <pre> <code class="language-java">WebClient webClient = WebClient.builder() .baseUrl(GITHUB_API_BASE_URL) .defaultHeader(HttpHeaders.CONTENT_TYPE, GITHUB_V3_MIME_TYPE) .filter(ExchangeFilterFunctions .basicAuthentication(username, token)) .filter(logRequest()) .build();</code></pre> 这里是<code>logRequest()</code>过滤器功能的实现- <pre> <code class="language-java">private ExchangeFilterFunction logRequest() { return (clientRequest, next) -> { logger.info("Request: {} {}", clientRequest.method(), clientRequest.url()); clientRequest.headers() .forEach((name, values) -> values.forEach(value -> logger.info("{}={}", name, value))); return next.exchange(clientRequest); }; }</code></pre> <h4 style="margin-left:0px; margin-right:0px; text-align:start">3.使用ofRequestProcessor()和ofResponseProcessor()工厂方法来创建过滤器</h4> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">ExchangeFilterFunction API提供两个名为工厂方法<code>ofRequestProcessor()</code>和<code>ofResponseProcessor()</code>用于创建分别截获该请求和响应滤波器的功能。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff"><code>logRequest()</code>我们在前一节中创建的过滤器函数可以使用<code>ofRequestProcessor()</code>这种工厂方法创建-</span></span></span></p> <pre> <code class="language-java">private ExchangeFilterFunction logRequest() { ExchangeFilterFunction.ofRequestProcessor(clientRequest -> { logger.info("Request: {} {}", clientRequest.method(), clientRequest.url()); clientRequest.headers() .forEach((name, values) -> values.forEach(value -> logger.info("{}={}", name, value))); return Mono.just(clientRequest); }); } </code></pre> 如果您想拦截WebClient响应,则可以使用该<code>ofResponseProcessor()</code>方法创建像这样的过滤器功能 - <pre> <code class="language-java">private ExchangeFilterFunction logResposneStatus() { return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> { logger.info("Response Status {}", clientResponse.statusCode()); return Mono.just(clientResponse); }); }</code></pre> <h2 style="margin-left:0px; margin-right:0px; text-align:start">处理WebClient错误</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">只要接收到状态码为4xx或5xx的响应<code>retrieve()</code>,WebClient中的方法<code>WebClientResponseException</code>就会抛出一个。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">您可以使用<code>onStatus()</code>像这样的方法来自定义,</span></span></span><br />  </p> <pre> <code class="language-java">public Flux<GithubRepo> listGithubRepositories() { return webClient.get() .uri("/user/repos?sort={sortField}&direction={sortDirection}", "updated", "desc") .retrieve() .onStatus(HttpStatus::is4xxClientError, clientResponse -> Mono.error(new MyCustomClientException()) ) .onStatus(HttpStatus::is5xxServerError, clientResponse -> Mono.error(new MyCustomServerException()) ) .bodyToFlux(GithubRepo.class); }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">请注意,与<code>retrieve()</code>方法不同,该<code>exchange()</code>方法在4xx或5xx响应的情况下不会引发异常。您需要自己检查状态代码,并以您想要的方式处理它们。</span></span></span></p> <h4 style="margin-left:0px; margin-right:0px; text-align:start">使用<code>@ExceptionHandler</code>控制器内部的WebClientResponseExceptions处理</h4> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">您可以<code>@ExceptionHandler</code>在控制器内部使用这种方式来处理<code>WebClientResponseException</code>并返回适当的响应给客户端 -</span></span></span></p> <pre> <code class="language-java">@ExceptionHandler(WebClientResponseException.class) public ResponseEntity<String> handleWebClientResponseException(WebClientResponseException ex) { logger.error("Error from WebClient - Status {}, Body {}", ex.getRawStatusCode(), ex.getResponseBodyAsString(), ex); return ResponseEntity.status(ex.getRawStatusCode()).body(ex.getResponseBodyAsString()); }</code></pre> <h2 style="margin-left:0px; margin-right:0px; text-align:start">使用Spring 5 WebTestClient测试Rest API</h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">WebTestClient包含类似于WebClient的请求方法。另外,它还包含检查响应状态,标题和正文的方法。您也可以像<code>AssertJ</code>使用WebTestClient 一样使用断言库。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:rgba(0, 0, 0, 0.87)"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif"><span style="background-color:#ffffff">查看以下示例以了解如何使用WebTestClient执行其他API测试 -</span></span></span></p> <blockquote> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif">提示:<a href="http://www.leftso.com/resource/1011.html" target="_blank" >项目源码下载</a></span></p> </blockquote>