搜索词>>Spring Boot WebFlux 耗时0.0230
  • 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

    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 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教程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 和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 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 mybatis 整合_spring boot mybatis3 事物配置

    spring boot mybatis 整合过程中事物得配置详细讲解,spring boot mybatis3 事物配置详细讲解<h2>引言</h2>     通过之前spring boot mybatis 整合的讲解: <blockquote> <a rel="" target="_blank"href="http://www.leftso.com/blog/80.html" rel="" target="_blank" title="spring boot整合mybaties">spring boot mybaties整合  </a>(spring boot mybaties 整合 基于Java注解方式写sql,无需任何得mapper xml文件) <p><a rel="" target="_blank"href="http://www.leftso.com/blog/133.html" rel="" target="_blank" title="spring boot mybatis 整合_spring boot与mybaties的使用">spring boot mybatis 整合_spring boot与mybaties的使用</a>  (spring boot mybaties 整合 xml mapper方式,也是实际应用最多得方式)</p> </blockquote> 我们对于spring boot mybaties 整合有了一个基础的认知。这里主要正对上面得两篇文章中spring boot mybaties整合讲解得一个扩展学习,事物的配置,整合到spring 的事物控制中。 <h2>一.环境准备</h2> 本博客讲沿用上面的项目进行进一步讲解 <h2>二.实战编码</h2> <h3>2.1 spring boot 核心配置文件application.properties</h3> <pre> <code class="language-html">#==================DataSource Config Start================== #默认采用Tomcat-jdbc-pool性能和并发最好,注意查看maven依赖中是否有tomcat-jdbc #name #spring.datasource.name=test #url #spring.datasource.url=jdbc:sqlserver://192.168.xxx.xxx;instanceName=sql_03;DatabaseName=edu;integratedSecurity=false spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8 #DriverClass #spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver spring.datasource.tomcat.driver-class-name=com.mysql.jdbc.Driver #DB username spring.datasource.tomcat.username=root #DB password spring.datasource.tomcat.password=root #最大连接数量 spring.datasource.tomcat.max-active=150 #最大闲置连接数量 spring.datasource.tomcat.max-idle=20 #最大等待时间 #spring.datasource.tomcat.max-wait=5000 #==================DataSource Config End================== #==================mybaties Config Start================== #ORM Bean Package mybatis.type-aliases-package=com.example.pojo mybatis.mapper-locations=classpath:/mapper/*.xml #打印mybatiesSql语句 logging.level.com.example.mapper=DEBUG #==================mybaties Config End ================== #模板引擎配置缓存为FALSE。开发调试用 spring.thymeleaf.cache=false </code></pre> 这里注意关注数据连接配置和mybaties的xml mapper文件配置。<br />   <h3>2.2spring boot mybaties 整合 事物关键配置</h3> <strong>MyBatiesConfig.java</strong> <pre> <code class="language-java">package com.example.config; import javax.sql.DataSource; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; /** * mybaties配置扫描mapper路径 * * @author leftso * */ @Configuration @MapperScan(basePackages = { "com.example.mapper" }) /** 注意,这个注解是扫描mapper接口不是xml文件,使用xml模式必须在配置文件中添加xml的配置 **/ @EnableTransactionManagement /** * 启用事物管理 ,在需要事物管理的service类或者方法上使用注解@Transactional **/ public class MyBatiesConfig { @Autowired private DataSource dataSource; /** * 配合注解完成事物管理 * * @return */ @Bean public PlatformTransactionManager annotationDrivenTransactionManager() { return new DataSourceTransactionManager(dataSource); } } </code></pre> 注意必须把当前的数据源配置进入spring的注解事物管理器。否则通过spring框架的注解标签@Transactional是不会有事物作用的。<br />   <h2>三.事物演示</h2> <h3>3.1编写测试代码</h3> <pre> <code class="language-java">package com.example.test; 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.test.context.junit4.SpringRunner; import com.example.mapper.UserMapper; import com.example.pojo.User; @RunWith(SpringRunner.class) @SpringBootTest public class TransactionalTest { @Autowired private UserMapper userMapper; @Test public void name() { User user=new User("leftso", "男", 1); userMapper.insert(user); int t=1/0; System.out.println(t); } } </code></pre> 执行前查询数据库:<br /> <img alt="执行前查询数据库:" class="img-thumbnail" src="/resources/assist/images/blog/5f8c20ca282f4b1c855725c473a0a5ff.png" /><br /> 执行测试代码并观察eclipse的控制台和数据库的数据查询结果:<br /> <img alt="eclipse的控制台" class="img-thumbnail" src="/resources/assist/images/blog/552fd284c5b04a03ab408b68f7e56532.png" /><br /> <img alt="数据库查询结果" class="img-thumbnail" src="/resources/assist/images/blog/55507a3964bb4482b00c9a6bd95b2f75.png" /><br /> 很明显在报错的情况下,数据还是插入进了数据库。这并不是我们正常业务想要的结果。 <h3>3.2编辑测试代码,添加spring框架的事物注解</h3> <pre> <code class="language-java">package com.example.test; 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.test.context.junit4.SpringRunner; import org.springframework.transaction.annotation.Transactional; import com.example.mapper.UserMapper; import com.example.pojo.User; @RunWith(SpringRunner.class) @SpringBootTest public class TransactionalTest { @Autowired private UserMapper userMapper; @Test @Transactional public void name() { User user=new User("测试哈哈", "女", 2); userMapper.insert(user); int t=1/0; System.out.println(t); } } </code></pre> <br /> 执行代码并观察eclipse和数据库:<br /> <img alt="spring boot mybaties 整合测试eclipse控制台输出结果" class="img-thumbnail" src="/resources/assist/images/blog/db0656c4eb684dfaad995363bf77a6ac.png" /><br /> <br /> <img alt="spring boot mybaties 整合 测试数据库查询结果" class="img-thumbnail" src="/resources/assist/images/blog/479ffa98d4a64ef888e6cf6654e0f956.png" /><br /> <br /> 这次的操作姿势似乎对了。在报错的情况下数据并没有插入数据库。我们仔细观察spring 控制台输出的日志可以发现事物已经在spring的控制下回滚了。<br /> <img alt="spring boot mybaties 整合 测试异常回滚eclipse控制台日志输出结果" class="img-thumbnail" src="/resources/assist/images/blog/f2e785539cff4ef7b04a86253f94ac00.png" /><br /> 从上图也可以看到回滚的日志<br />  
  • Spring Boot 2.0 绑定属性资源文件 Spring Boot 2.0 读取配置文件值 Spring Boot 2.0获取配置文件值

    Spring Boot 2.0 绑定properties属性资源文件 Spring Boot 2.0 读取properties配置文件值 Spring Boot 2.0获取properties配置文件值Spring Boot 2.0 绑定properties属性资源文件 Spring Boot 2.0 读取配置文件值 Spring Boot 2.0获取配置文件值<br /> 自Spring Boot首次发布以来,就可以使用<code>@ConfigurationProperties</code>注释将属性绑定到类。也可以用不同的形式指定属性名称。 例如,person.first-name,person.firstName和PERSON_FIRSTNAME可以互换使用。 我们称这个功能为“宽松绑定”。<br /> <br /> 不幸的是,在Spring Boot 1.x中,“宽松绑定”实际上有点过于宽松。 确切地定义绑定规则是什么以及什么时候可以使用特定格式是相当困难的。 我们也开始获得有关我们的1.x实施难以解决的问题的报告。 例如,在Spring Boot 1.x中,不可能将项目绑定到java.util.Set。<br /> 所以,在Spring Boot 2.0中,我们已经着手重新设计绑定发生的方式。 我们添加了几个新的抽象,并且我们开发了一个全新的绑定API。 在这篇博文中,我们介绍了一些新的类和接口,并描述了为什么添加了它们,它们做了什么,以及如何在自己的代码中使用它们。 <h2>Property Sources</h2> 如果你一直在使用Spring,你可能很熟悉<code>Environment</code>抽象类。这个接口是一个<code>PropertyResolver</code>,它可以让你从一些底层的<code>PropertySource</code>实现中解析属性。<br /> Spring框架为常见的东西提供<code>PropertySource</code>实现,例如系统属性,命令行标志和属性文件。 Spring Boot 会以对大多数应用程序有意义的方式自动配置这些实现(例如,加载application.properties)。 <h2>Configuration Property Sources</h2> Spring Boot 2.0不是直接使用现有的<code>PropertySource</code>接口进行绑定,而是引入了一个新的<code>ConfigurationPropertySource</code>接口。 我们引入了一个新的接口,为我们提供了一个合理的地方来实施放松绑定规则,这些规则以前是活页夹的一部分<br /> 接口的主要API非常简单: code:>>ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name);<br /> 还有一个<code>IterableConfigurationPropertySource</code>变相的实现了<code>Iterable </code>接口,以便您可以发现源包含的所有名称。<br /> <br /> 通过使用以下代码,可以将Spring <code>Environment</code>调整为<code>ConfigurationPropertySources</code><br /> Iterable sources = ConfigurationPropertySources.get(environment);<br /> 如果您需要它,我们还提供一个简单的<code>MapConfigurationPropertySource</code>实现。 <h2>Configuration Property Names</h2> 事实证明,如果将其限制为一个方向,放宽属性名称的实现更容易实现。 您应该始终使用规范形式访问代码中的属性,而不管它们在基础源中的表示方式如何。<br /> ConfigurationPropertyName类强制执行这些规范的命名规则,这些规则基本归结为“使用小写的kebab-case名称”。<br /> 因此,例如,即使在基础源中使用person.firstName或PERSON_FIRSTNAME,也应该将代码中的属性称为person.first-name。<br /> Origin Support起源支持<br /> 一个Origin是Spring Boot 2.0中引入的一个新接口,可以让您精确定位从某个值加载的确切位置。 有许多Origin实现,可能最有用的是TextResourceOrigin。 这提供了加载的资源的详细信息,以及值的行号和列号。 <pre> <code class="language-html">*************************** APPLICATION FAILED TO START *************************** Description: Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'person' to scratch.PersonProperties failed: Property: person.name Value: Joe Origin: class path resource [application.properties]:1:13 Reason: length must be between 4 and 2147483647 Action: Update your application's configuration </code></pre>   <h2>Binder API</h2> Binder类(在org.springframework.boot.context.properties.bind中)可以让你获取一个或多个ConfigurationPropertySource并从它们中绑定一些东西。 更准确地说,一个Binder采用一个Bindable并返回一个BindResult。<br /> Bindable<br /> Bindable可能是现有的Java bean,类类型或复杂的ResolvableType(如List )。 这里有些例子 <pre> <code class="language-java">Bindable.ofInstance(existingBean); Bindable.of(Integer.class); Bindable.listOf(Person.class); Bindable.of(resovableType);</code></pre> <br /> Bindable也用于携带注释信息,但通常不需要关心这一点。 <h2>BindResult</h2> <br /> 绑定器不是直接返回一个绑定对象,而是返回一个名为BindResult的东西。 类似于Java 8 Streams返回Optional的方式,BinderResult表示可能绑定或可能未绑定的内容。<br /> <br /> 如果您尝试获取未绑定对象的实际结果,则会引发异常。 我们还提供了一些方法,可以让您在没有任何约束或映射到不同类型时提供替代值 <pre> <code class="language-java">var bound = binder.bind("person.date-of-birth",Bindable.of(LocalDate.class)); // Return LocalDate or throws if not bound bound.get(); // Return a formatted date or "No DOB" bound.map(dateFormatter::format).orElse("No DOB"); // Return LocalDate or throws a custom exception bound.orElseThrow(NoDateOfBirthException::new);</code></pre> <h2>Formatting and Conversion </h2> 大多数ConfigurationPropertySource实现将其基础值作为字符串公开。 当Binder需要将源值转换为其他类型时,它将委托给Spring的ConversionService API。<br /> 如果您需要调整值的转换方式,则可以自由使用格式化程序注释(如@NumberFormat或@DateFormat)。<br /> <br /> Spring Boot 2.0还引入了一些对绑定特别有用的新注释和转换器。 例如,您现在可以将诸如4s之类的值转换为持续时间。 有关详细信息,请参阅org.springframework.boot.convert包。 <h2>BindHandler</h2> 有时候,绑定时可能需要实现额外的逻辑,而BindHandler接口提供了一个很好的方法来实现这一点。 每个BindHandler都可以实现onStart,onSuccess,onFailure和onFinish方法来覆盖行为。<br /> <br /> Spring Boot提供了一些处理程序,主要用于支持现有的@ConfigurationProperties绑定。 例如,ValidationBindHandler可用于对绑定对象应用Validator验证。 <h2>@ConfigurationProperties</h2> 正如本文开始时提到的,@ConfigurationProperties从一开始就一直是Spring Boot的特色。 @ConfigurationProperties很可能仍然是大多数人执行绑定的方式。<br /> <br /> 尽管我们重写了整个绑定过程,但大多数人在升级Spring Boot 1.5应用程序时似乎并没有太多问题。 只要您遵循迁移指南中的建议,您应该会发现事情继续良好。<br /> 如果您在升级应用程序时发现问题,请在GitHub问题跟踪器上以小样本向他们报告,以便重现问题。 <h2>Future Work</h2> 我们计划在Spring Boot 2.1中继续开发Binder,我们希望支持的第一个特性是不可变的配置属性。 如果当前需要getter和setter的配置属性可以使用基于构造函数的绑定,那将是非常好的: <pre> <code class="language-java">public class Person { private final String firstName; private final String lastName; private final LocalDateTime dateOfBirth; public Person(String firstName, String lastName, LocalDateTime dateOfBirth) { this.firstName = firstName; this.lastName = lastName; this.dateOfBirth = dateOfBirth; } // getters }</code></pre> 我们认为构造函数绑定也可以很好地处理Kotlin数据类。<br /> <br /> 小结<br /> 我们希望您能够在Spring Boot 2.0中发现新的绑定功能,并且您会考虑升级现有的Spring Boot应用程序。
  • spring boot 整合redis实现spring的缓存框架

    spring boot 1.5整合redis实现spring的缓存框架,spring boot,redisspring boot 整合redis实现spring的缓存框架<br /> <br /> 1.创建一个spring boot项目<br /> <img alt="项目" class="img-thumbnail" src="/resources/assist/images/blog/87917c2a16564cc08bf8e7670ec943ea.jpg" /><br /> 2.配置pom.xml文件 <pre> <code class="language-xml"><?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.leftso</groupId> <artifactId>demo-springboot-redis</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>demo-springboot-redis</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> </code></pre> <br /> 3.在spring boot的application配置文件中配置redis的相关信息 <pre> <code>####################Redis 配置信息 ########################## # Redis数据库分片索引(默认为0) spring.redis.database=0 # Redis服务器地址 spring.redis.host=10.1.1.134 # Redis服务器连接端口 spring.redis.port=6379 # Redis服务器连接密码(默认为空) spring.redis.password= # 连接池最大连接数(使用负值表示没有限制) spring.redis.pool.max-active=8 # 连接池最大阻塞等待时间(使用负值表示没有限制) spring.redis.pool.max-wait=-1 # 连接池中的最大空闲连接 spring.redis.pool.max-idle=8 # 连接池中的最小空闲连接 spring.redis.pool.min-idle=0 # 连接超时时间(毫秒) spring.redis.timeout=0</code></pre> <br /> 4.配置redis整合入spring的缓存框架 <pre> <code class="language-java">package com.leftso.config; import java.lang.reflect.Method; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; @Configuration @EnableCaching // 继承CachingConfigurerSupport并重写方法,配合该注解实现spring缓存框架的启用 public class RedisConfig extends CachingConfigurerSupport { /** 载入通过配置文件配置的连接工场 **/ @Autowired RedisConnectionFactory redisConnectionFactory; @SuppressWarnings("rawtypes") @Autowired RedisTemplate redisTemplate; @Bean RedisTemplate<String, Object> objRedisTemplate() { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); return redisTemplate; } /* * (non-Javadoc) * * @see org.springframework.cache.annotation.CachingConfigurerSupport# * cacheManager() */ @Bean // 必须添加此注解 @Override public CacheManager cacheManager() { RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate); // 设置缓存过期时间 // redisCacheManager.setDefaultExpiration(60);//秒 return redisCacheManager; } /** * 重写缓存的key生成策略,可根据自身业务需要进行自己的配置生成条件 * * @see org.springframework.cache.annotation.CachingConfigurerSupport# * keyGenerator() */ @Bean // 必须项 @Override public KeyGenerator keyGenerator() { return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { StringBuilder sb = new StringBuilder(); sb.append(target.getClass().getName()); sb.append(method.getName()); for (Object obj : params) { sb.append(obj.toString()); } return sb.toString(); } }; } } </code></pre> <br /> 5.创建一个测试的pojo和一个测试的controller <pre> <code class="language-java">package com.leftso.pojo; import java.io.Serializable; /** * * <pre> * [Summary] * 测试用简单类型 * [Detail] * TODO * [Author] * XQLEE * [Version] * v1.0 * 2017年5月8日上午9:58:42 * </pre> */ public class Address implements Serializable{ /** * */ private static final long serialVersionUID = 1L; private String id; private String city; private String detail; public Address() { } public Address(String id, String city, String detail) { super(); this.id = id; this.city = city; this.detail = detail; } /** * @return the id */ public String getId() { return id; } /** * @param id * the id to set */ public void setId(String id) { this.id = id; } /** * @return the city */ public String getCity() { return city; } /** * @param city * the city to set */ public void setCity(String city) { this.city = city; } /** * @return the detail */ public String getDetail() { return detail; } /** * @param detail * the detail to set */ public void setDetail(String detail) { this.detail = detail; } } </code></pre>   <pre> <code class="language-java">package com.leftso.controller; import org.springframework.cache.annotation.Cacheable; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import com.leftso.pojo.Address; @RestController public class CacheTestCtrl { @GetMapping("/getAddr") @Cacheable(value = "address-cache") // 设置缓存的名称,也可以通过key属性指定缓存的key,keyGenerator指定key生成策略器(keyGenerator一般推荐在重写CachingConfigurerSupport类里面的方法适合全局指定) public Address getAddress() { Address addr = new Address("001", "重庆市", "渝北区"); System.out.println("==========你看见这句话表示没有缓存时候打印出来的========"); return addr; } } </code></pre> <br /> 6.启动spring boot项目,访问地址localhost:8080/getAddr<br /> 第一次访问将会在控制台中看到以下输出:<br /> <img alt="1" class="img-thumbnail" src="/resources/assist/images/blog/8273e3645def48cfb209a482857ff0ab.jpg" /><br /> <br /> 后面多次刷新都不会进入到该方法中去读取信息,而是通过缓存直接读取了缓存信息。<br /> <br /> <br /> 至此spring boot整合redis 实现基本的缓存已经完成。
  • Spring Boot validation整合hibernate validator实现数据验证

    Spring Boot validation整合hibernate validator实现数据验证,Spring Boot validation使用说明,Spring Boot validation使用教程<h2>引言</h2>     这里主要讲解在Spring  Boot项目中整合hibernate validator框架实现Spring  Boot项目的validation 验证机制。方便后端验证前端或者接口传递过来的数据格式是否正确。 <h2>一.准备环境</h2> <ul> <li>jdk1.8+(Spring Boot项目推荐使用1.8)</li> <li>eclipse(或者你喜欢的IDE)</li> <li>maven 3+</li> </ul> <h2>二.编码实现Spring Boot validation</h2> <h3>2.1创建一个spring boot项目并添web模块和validation模块</h3> 项目结构图如下:<br /> <img srcset="" width="" size="" class="img-thumbnail" alt="spring boot项目结果图" src="/resources/assist/images/blog/017be0b2e4e848cb9bc98279929d073a.png" /><br /> 项目的依赖文件: <pre> <code class="language-xml"><?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>net.xqlee.project.demo</groupId> <artifactId>demo-springboot-hibernate-validator</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>demo-springboot-hibernate-validator</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.8.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter- </artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> </code></pre> <h3> </h3> <blockquote> <h3>注意:</h3> <pre> <code class="language-xml"> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency></code></pre> 该模块会自动加载hibernate-validation依赖。不要自己手动配置谨防依赖问题</blockquote> <h3><br /> 2.2编写一个测试的简单对象POJO</h3> <pre> <code class="language-java">package net.xqlee.project.demo.pojo; import javax.validation.constraints.Pattern; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.constraints.NotEmpty; import org.hibernate.validator.constraints.Range; public class User { @NotBlank(message = "用户名称不能为空。") private String name; @Range(max = 150, min = 1, message = "年龄范围应该在1-150内。") private Integer age; @NotEmpty(message = "密码不能为空") @Length(min = 6, max = 8, message = "密码长度为6-8位。") @Pattern(regexp = "[a-zA-Z]*", message = "密码不合法") private String password; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } </code></pre> 细心的朋友可能已经发现一些属性上的注解。验证框架正是实现了这些注解的具体验证。hibernate在java的JSR-303标准上还添加了一些额外的验证注解实现。这些实现都为我们后端验证数据取得了巨大的方便。 <blockquote> <p>提示:<br /> 验证注解推荐使用</p> <p>javax.validation.constraints包下的注解,不要使用hibernate的<br /> 在2.0中hibernate的很多注解已经弃用,但是</p> <p>javax.validation.constraints包下的注解增多增强<br /> 一种规范性的趋势</p> </blockquote> <h3>2.3编写一个controller用于测试验证机制</h3> <pre> <code class="language-java">package net.xqlee.project.demo.controller; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.validation.BindingResult; import org.springframework.validation.ObjectError; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import net.xqlee.project.demo.pojo.User; @RestController public class ValidatorController { private static final Logger log = LoggerFactory.getLogger(ValidatorController.class); /** * 验证框架使用测试 * * @param user * @param result */ @PostMapping("v/t1.json") public void v1(@Validated User user, BindingResult result) { StringBuilder sBuilder = new StringBuilder(); sBuilder.append("\n"); if (result.hasErrors()) { List<ObjectError> list = result.getAllErrors(); for (ObjectError error : list) { log.info(error.getCode() + "---" + error.getArguments() + "---" + error.getDefaultMessage()); sBuilder.append(error.getDefaultMessage()); sBuilder.append("\n"); } } log.info(sBuilder.toString()); } } </code></pre> <blockquote> <p>提示:<br /> @Validated (org.springframework.validation.annotation)<br /> 或者@Valid(javax.validation)<br /> 验证注解推荐写在方法的参数列表中,例如:<br /> Object   methodName(@Valid Object params){...}<br /> Object   methodName(@Validated Object params){...}<br /> <br />  </p> </blockquote> <h2><br /> 三.演示</h2> <h3>3.1启动spring boot项目</h3> <h3>3.2通过工具postmain进行提交数据验证</h3> <strong>第一组测试:</strong><br /> <br /> 提交全部为空数据:<br /> <img srcset="" width="" size="" class="img-thumbnail" alt="spring boot validation测试数据1" src="/resources/assist/images/blog/8d1b26436c0f42b382e4aa7378e4c466.png" /><br /> 观察eclipse的控制台输出:<br /> <img srcset="" width="" size="" class="img-thumbnail" alt="Spring Boot validation 控制台输出1" src="/resources/assist/images/blog/6b5e924b8c57446ab125be2043cec0e3.png" /><br /> 可以看到非空验证已经实现。<br /> <br /> <strong>第二组测试:</strong><br /> <br /> 接下来输入一个非空的名称和密码<br /> <img srcset="" width="" size="" class="img-thumbnail" alt="spring boot validation测试数据2" src="/resources/assist/images/blog/d86badcd7bc44cdba5080e8f87406023.png" /><br /> 观察eclipse控制台:<br /> <img srcset="" width="" size="" class="img-thumbnail" alt="Spring Boot validation 控制台输出2" src="/resources/assist/images/blog/87f994b3a6eb4bf9a77f3ce652471c11.png" /><br /> 可以看到名称的验证错误信息已经没有说明已经输入正确。但是密码却没有通过正则表达式的验证所以报错不合法,在对象上我们设置的密码只能是大写的字母。所以刚才输入的全数字不合法。<br /> <br /> <strong>第三组测试:</strong><br /> <br /> 接下来输入正常的密码<br /> <img srcset="" width="" size="" class="img-thumbnail" alt="spring boot validation测试数据3" src="/resources/assist/images/blog/40be7f44b6144ade956b3453ae420766.png" />观察控制台:<br /> <br /> <img srcset="" width="" size="" class="img-thumbnail" alt="Spring Boot validation 控制台输出3" src="/resources/assist/images/blog/2be73b7acb544f22845efacbb9c58a6f.png" /><br /> 可以看到已经没有错误信息。验证通过