Spring WebFlux入门指南.docx
- 文档编号:27855063
- 上传时间:2023-07-05
- 格式:DOCX
- 页数:16
- 大小:401.19KB
Spring WebFlux入门指南.docx
《Spring WebFlux入门指南.docx》由会员分享,可在线阅读,更多相关《Spring WebFlux入门指南.docx(16页珍藏版)》请在冰豆网上搜索。
SpringWebFlux入门指南
SpringWebFlux入门指南
SpringWebFlux入门指南
对SpringWebFlux的响应能力感兴趣却不知道从何入手?
这里对其学习路线,处理程序和请求进行了深入探讨
最近SpringBoot2.0的GA版本发布了,所以我决定近期写我的第一篇关于Spring的文章。
自从其发布以来,我在如何使用它的教程中看到了越来越多的提到了SpringWebFlux。
但是在通读完这些教程文档然后自己去实现后,我发现从我阅读的文章和教程中的代码进一步去写代码做点比从后端返回一个字符串稍微有点意思的事情都有些困难。
现在,我希望那样说没有搬起石头砸自己的脚,因为你们也可能对于本篇文章中使用的代码做出同样的批评。
但是这里我尽量按照你们在实际工作学习中使用的方式来讲解SpringWebFlux。
在继续学习之前,首先这里提到的WebFlux,究竟是什么?
SpringWebFlux是SpringMVC的一个完全非阻塞的响应式替代方案。
它允许在不增加硬件资源的情况下更好的进行垂直伸缩。
因为是响应式的,它采用了ReactiveStreams从而允许异步处理从服务端调用返回的数据。
这意味着我们将会看到更少的List,Collection,甚至单个对象。
作为替代,我们将看到他们的响应式等价形式,比如Flux和Mono(来自于Reactor)。
我不会在什么是ReactiveStreams上深入探讨,坦白地说,在我向别人(即便是我自己)解释它之前,我还需要深入研究下它。
相反,让我们回过头来集中关注WebFlux。
像平时一样,我使用SpringBoot在本教程中编写代码。
以下是我在这篇文章中用到的依赖。
虽然没有在上面的依赖片段中包含进来,spring-boot-starter-parent还是使用到了,并且可以升级到2.0.0版本。
因为本教程是关于WebFlux的,包含spring-boot-starter-webflux显然是一个好主意。
spring-boot-starter-data-cassandra-reactive也包括在内,因为我们将使用它作为示例应用程序的数据库-它是少数几个有响应式支持的数据库之一(在编写本文时)。
通过将这些依赖关系一起使用,我们的应用程序将是从前台到后端完全响应式的。
WebFlux引入了一种不同的方式来处理请求,而不是使用SpringMVC中使用的@Controller
或@RestController编程模型。
与此同时,它并没有取代它。
而是升级为允许使用响应式类型。
这将允许你使用Spring时保持和之前一样的格式,但是需要对返回类型做一些修改,返回Flux或Monos类型。
下面是一个很有创造性的示例。
@RestController
publicclassPersonController{
privatefinalPersonRepositorypersonRepository;
publicPersonController(PersonRepositorypersonRepository){this.personRepository=personRepository;
}
@GetMapping("/people")publicFlux
returnpersonRepository.findAll();
}
@GetMapping("/people/{id}")
Mono
}
}
对我来说,这看起来非常熟悉,而且乍一看,它与标准的SpringMVCcontroller看起来没有任何区别,但是通过阅读方法后,我们可以看到与我们通常所期望的不同的返回类型。
在这个例子
中,PersonRepository必须是响应式库,因为我们已经能够直接返回他们的搜索查询结果。
作为参考,响应式库对于集合将返回一个Flux类型,对于单数实体返回一个Mono类型。
不过,注释方法并不是我想在这篇文章中关注的内容。
这对我们来说不够酷,时髦。
没有足够的lambda表达式来满足我们以更有效的方式编写Java的渴望。
但SpringWebFlux为我们(的这种渴望)提供了支持。
它提供了一种替代方法来路由和处理对我们的服务器的请求,轻松使用lambdas编写路由器功能。
我们来看一个例子。
@Configuration
publicclassPersonRouter{@Bean
publicRouterFunction
returnRouterFunctions.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:
:
get
ByCountry);
}
}
PersonHandler中有所有方法的路由,我们将在后面介绍。
我们创建了一个将处理我们路由的bean。
为了设置路由功能,我们使用了名为RouterFunctions的类,为我们提供了一些静态方法。
但现在,我们只关心它的route方法。
以下是route方法的签名:
publicstatic
//stuff
}
该方法显示它接受一个RequestPredicate参数和一个HandlerFunction参数并输出一个RouterFunction。
RequestPredicate是我们用来指定路由的行为,比如我们的处理函数的路径,它是什么类型的请求以及它可以接受的输入类型。
由于我使用静态导入将所有内容读得更清晰,所以一些重要信息对于你们是不可见的。
要创建一个RequestPredicate,我们应该使用RequestPredicates(复数),这是一个静态帮助类,为我们提供了我们需要的所有方法。
就我个人而言,我建议静态导入RequestPredicates。
否则,随着您可能需要使用RequestPredicates静态方法的次数增多,您的代码将变得一团糟。
在上面的例子
中,GET,POST,PUT,DELETE,accept和contentType都是静态的RequestPredicates方法。
下一个参数是一个HandlerFunction,它是一个功能接口。
这里有三条重要信息。
它有一个通用类型的
使用这些,我们可以确定我们需要传递一个返回Mono
这显然对我们的处理函数返回的内容产生严重的约束,因为它们必须满足这个要求,否则它们将不适合以这种格式使用。
最后,输出是一个RouterFunction。
这可以返回并用于路由到我们指定的任何函数。
但通常情况下,我们希望一次将很多不同的请求发送给各种处理程序,WebFlux满足了(我们的)这个需求。
由于路由返回一个RouterFunction,并且RouterFunction也有它自己的路由方法可用,andRoute,我们可以将这些调用链接在一起,并继续添加我们需要的所有额外路由。
如果我们再回头看一下上面的PersonRouter示例,我们可以看到这些方法以REST动词命名,如GET和POST,它们定义了处理程序将要执行的请求的路径和类型。
例如,如果我们以第一个GET请求为例,它将路由到具有路径变量名称id(路径变量由{id}表示)的/people。
同时,返回的内容的类型,特别是APPLICATION_JSON(来自MediaType的静态字段)是使用accept方法定义的。
如果使用不同的路径,则不会被处理。
如果路径正确,但Accept头不是可接受的类型之一,则请求将失败。
在继续之前,我想了解一下accept和contentType方法。
这两个设置请求首部-accept匹配到Accept首部和ContentType匹配到Content-Type。
Accept头定义了响应可以接受的媒体类型,比如我们返回的表示Person对象的JSON将它设置为APPLICATION_JSON(实际头中的application/json)是合理的。
Content-Type具有相同的用法,但是描述了什么媒体类型在发送的请求的主体内。
这就是为什么只有POST和PUT动词包含contentType的原因,因为其他人在他们的身体中没有任何东西。
DELETE不包含accept和contentType,所以我们可以得出结论,它既没有期望返回任何东西,也没有在它的请求体中包含任何东西。
现在我们已经知道如何设置路由了,接下来我们来看看如何编写处理传入请求的处理程序方法。
以下是处理前面示例中定义的路由的所有请求的代码。
publicPersonHandler(PersonManagerpersonManager){this.personManager=personManager;
}
publicMono
.flatMap(p->ok().contentType(APPLICATION_JSON).body(fromPublisher(person,Person.class)))
.switchIfEmpty(notFound().build());
}
publicMono
.body(fromPublisher(personManager.findAll(),Person.class));
}
publicMono
finalMono
.findById(id)
.flatMap(
old->
ok().contentType(APPLICATION_JSON)
.body(
fromPublisher(person
.map(p->newPerson(p,id))
.flatMap(p->personManager.update(old,p)),Person.class)))
.switchIfEmpty(notFound().build());
}
publicMono
returncreated(UriComponentsBuilder.fromPath("people/"+id).build().toUri())
.contentType(APPLICATION_JSON)
.body(
fromPublisher(
person.map(p->newPerson(p,id)).flatMap(personManager:
:
save),Person.class));
}
publicMono
.findById(id)
.flatMap(p->noContent().build(personManager.delete(p)))
.switchIfEmpty(notFound().build());
}
publicMono
returnok().contentType(APPLICATION_JSON)
.body(fromPublisher(personManager.findAllByCountry(country),Person.class));
}
}
有一点非常明显的是缺少注释。
禁止@Component注解自动创建一个PersonHandlerbean,没有其他Spring注解。
我试图将大部分存储库逻辑保留在这个类之外,并通过经由PersonManager委托给它所包含的PersonRepository来隐藏对实体对象的任何引用。
如果你对PersonManager中的代码感兴趣,那么可以在我的GitHub上看到,关于它的进一步解释将被排除在这篇文章之外,所以我们可以专注于WebFlux本身。
好的,回到手头的代码。
让我们仔细看一下get和post方法来弄清楚发生了什么。
publicMono
.flatMap(p->ok().contentType(APPLICATION_JSON).body(fromPublisher(person,Person.class)))
.switchIfEmpty(notFound().build());
}
此方法用于从支持此示例应用程序的数据库中检索单个记录。
由于Cassandra是所选择的数据库,因此我决定使用UUID作为每个记录的主键,这使得测试这个示例有些恼人,但是没有什么问题是复制和粘贴无法解决的。
请记住,此GET请求的路径中包含路径变量。
通过在传递给方法的ServerRequest上使用pathVariable方法,我们可以通过提供变量的名称(在本例中为id)来提取它的值。
然后将ID转换为UUID,如果字符串格式不正确,则会引发异常。
我决定忽略这个问题,所以示例代码不会变得混乱。
一旦我们有了ID,我们就可以查询数据库中是否存在匹配的记录。
返回一个Mono
使用返回的Mono,我们可以根据它的存在输出不同的响应。
这意味着我们可以将有用的状态代码返回给客户端以跟随主体的内容。
如果记录存在,则flatMap返回一个带有OK状态的ServerResponse。
随着这种状态,我们想要输出记录。
为此,我们指定正文的内容类型,在这种情况下
为APPLICATION_JSON,并将记录添加到其中。
fromPublisher将我们的Mono
fromPublisher
是BodyInserters类的静态方法。
如果记录不存在,那么流程将移入switchIfEmpty块并返回NOTFOUND状态。
由于没有发现任何东西,身体可以保持为空,所以我们只需创建ServerResponse。
现在到了post处理程序。
publicMono
returncreated(UriComponentsBuilder.fromPath("people/"+id).build().toUri())
.contentType(APPLICATION_JSON)
.body(
fromPublisher(
person.map(p->newPerson(p,id)).flatMap(personManager:
:
save),Person.class));
}
即使从第一行开始,我们也可以看到get方法的工作方式已经不同了。
由于这是一个POST请求,它需要接受我们想要从请求主体持久化的对象。
当我们试图插入单个记录时,我们将使用请求的bodyToMono方法从正文中检索Person。
如果你正在处理多个记录,你可能会想使用bodyToFlux。
我们将使用创建的方法返回一个CREATED状态,该方法接受一个URI以确定插入记录的路径。
然后通过使用fromPublisher方法将新记录添加到响应正文中,然后遵循与get方法类似的设置。
构成Publisher的代码略有不同,但重要的是输出仍然是Mono
为了进一步解释如何完成插入,从请求中传入的Person使用我们生成的UUID映射到一个新Person,然后通过调用flatMap来传递以保存。
通过创建一个新的Person,我们只将值插入到我们允许的Cassandra中,在这种情况下-我们不希望从请求体传入的UUID。
所以说,这是关于处理程序。
显然,还有其他方法,我们没有涉及到。
它们的工作方式都不同,但都遵循相同的理念,如果需要,可以返回包含适当状态代码和记录的ServerResponse。
现在我们已经编写了所有我们需要的代码来获得基本的SpringWebFlux后端运行。
剩下的就是将所有配置绑定在一起,这对SpringBoot来说很简单。
@SpringBootApplicationpublicclassApplication{
publicstaticvoidmain(Stringargs[]){SpringApplication.run(Application.class);
}
}
我们应该研究如何实际使用代码,而不是在此处结束这篇文章。
Spring提供WebClient类来处理请求而不会阻塞。
我们现在可以利用它来测试应用程序,但也有一
个WebTestClient,我们可以在这里使用它。
在创建反应型应用程序时,您将使用WebClient,而不是使用阻塞RestTemplate。
下面是一些调用PersonHandler中定义的处理程序的代码。
publicclassClient{
privateWebClientclient=WebClient.create("http:
//localhost:
8080");publicvoiddoStuff(){
//POST
finalPersonrecord=newPerson(UUID.randomUUID(),"John","Doe","UK",50);finalMono
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()));
//GETclient
.get()
.uri("/people/{id}","a4f66fe5-7c1b-4bcf-89b4-93d8fcbc52a4")
.accept(APPLICATION_JSON)
.exchange()
.f
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Spring WebFlux入门指南 WebFlux 入门 指南