河南夏邑网站建设,免费站推广网站不用下载,梧州网站建设推荐,做问卷的网站Spring Boot 2.0最近在GA上线了#xff0c;所以我决定在相当长一段时间内写我的第一篇有关Spring的文章。 自发布以来#xff0c;我已经看到越来越多的提到Spring WebFlux以及有关如何使用它的教程。 但是#xff0c;在阅读完它们并尝试使它们自己工作之后#xff0c;我… Spring Boot 2.0最近在GA上线了所以我决定在相当长一段时间内写我的第一篇有关Spring的文章。 自发布以来我已经看到越来越多的提到Spring WebFlux以及有关如何使用它的教程。 但是在阅读完它们并尝试使它们自己工作之后我发现很难从我所阅读的帖子和教程中包含的代码过渡到编写代码该代码实际上比返回字符串更有趣从后端。 现在我希望我不会因为说到您可能对本文中使用的代码有同样的批评而无视我的脚步但这是我尝试给出一个实际上类似于Spring WebFlux的教程的尝试。您可能在野外使用的东西。 在继续之前以及在提到WebFlux之后实际上是什么 Spring WebFlux是Spring MVC的完全不阻塞的反应性替代方案。 它允许更好的垂直扩展而无需增加硬件资源。 现在它是反应性的它利用反应性流来异步处理从调用返回到服务器的数据。 这意味着我们将看到更少的List Collection或什至单个对象而是它们的反应等效项例如Flux和Mono 来自Reactor。 我不会深入探讨什么是Reactive Streams因为说实话在尝试向任何人解释它之前我需要自己更多地研究它。 相反让我们重新关注WebFlux。 我像往常一样使用Spring Boot在本教程中编写代码。 以下是我在本文中使用的依赖项。 dependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-webflux/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-cassandra-reactive/artifactIdversion2.0.0.RELEASE/version/dependency/dependencies 尽管我没有在上面的依赖代码片段中包含它但是使用了spring-boot-starter-parent 最终可以将其2.0.0.RELEASE到2.0.0.RELEASE版本。 作为本教程的主题是有关WebFlux的包括spring-boot-starter-webflux显然是一个好主意。 spring-boot-starter-data-cassandra-reactive也已包括在内因为我们将其用作示例应用程序的数据库因为它是在撰写本文时为数不多的具有响应支持的数据库之一。 通过一起使用这些依赖关系我们的应用程序可以从前到后完全反应。 WebFlux引入了一种不同的方式来处理请求而不是使用Spring MVC中使用的Controller或RestController编程模型。 但是它不能替代它。 相反它已被更新以允许使用反应类型。 这使您可以保持与使用Spring编写时相同的格式但是对返回类型进行一些更改因此可以返回Flux或Mono 。 以下是一个非常人为的示例。 RestController
public class PersonController {private final PersonRepository personRepository;public PersonController(PersonRepository personRepository) {this.personRepository personRepository;}GetMapping(/people)public FluxPerson all() {return personRepository.findAll();}GetMapping(/people/{id})MonoPerson findById(PathVariable String id) {return personRepository.findOne(id);}
} 在我看来这看起来很熟悉而且乍一看它与标准的Spring MVC控制器并没有什么不同但是在阅读完这些方法之后我们可以看到与通常期望的不同的返回类型。 在此示例中 PersonRepository必须是一个反应式存储库因为我们已经能够直接返回其搜索查询的结果以供参考反应式存储库将为集合返回Flux 对于单个实体返回Mono 。 注解方法不是我在本文中要关注的重点。 这对我们来说还不够酷和时髦。 没有足够的使用lambda来满足我们以更实用的方式编写Java的需求。 但是Spring WebFlux有我们的支持。 它提供了一种替代方法来路由和处理到我们服务器的请求该方法仅使用lambda来编写路由器功能。 让我们看一个例子。 Configuration
public class PersonRouter {Beanpublic RouterFunctionServerResponse 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);}
} 这些都是通往PersonHandler中方法的所有路由我们将在后面介绍。 我们创建了一个将处理路由的bean。 为了设置路由功能我们使用了命名良好的RouterFunctions类为我们提供了许多静态方法但是现在我们只对它的route方法感兴趣。 以下是route方法的签名。 public static T extends ServerResponse RouterFunctionT route(RequestPredicate predicate, HandlerFunctionT handlerFunction) {// stuff
} 该方法显示它与HandlerFunction一起接收RequestPredicate并输出RouterFunction 。 RequestPredicate是我们用来指定路由行为的内容例如处理程序函数的路径请求的类型以及可接受的输入类型。 由于我使用静态导入来使所有内容读起来更加清晰因此一些重要信息已对您隐藏了。 要创建RequestPredicate我们应该使用RequestPredicates 复数这是一个静态帮助器类为我们提供了所需的所有方法。 我个人确实建议静态导入RequestPredicates否则由于您可能需要使用RequestPredicates静态方法的次数而使代码混乱。 在上面的示例中 GET POST PUT DELETE accept和contentType都是静态RequestPredicates方法。 下一个参数是HandlerFunction 它是一个功能接口。 这里有3条重要信息它具有T extends ServerResponse的通用类型其handle方法返回MonoT并接受ServerRequest 。 使用这些我们可以确定我们需要传递一个返回MonoServerResponse 或其子类型之一的函数。 显然这对我们的处理程序函数返回的内容施加了严格的约束因为它们必须满足此要求否则将不适合以这种格式使用。 最后输出是RouterFunction 。 然后可以将其返回并将其用于路由到我们指定的任何函数。 但是通常我们希望将许多不同的请求立即路由到各种处理程序WebFlux可以满足这些请求。 由于route返回RouterFunction和事实RouterFunction也有提供自己的路由方法 andRoute 我们可以链通话在一起不断增加的所有额外的路线我们需要。 如果再回顾一下上面的PersonRouter示例我们可以看到这些方法以REST动词例如GET和POST命名它们定义了处理程序将采用的请求的路径和类型。 例如如果我们以第一个GET请求为例它将使用路径变量名称id 由{id}表示的路径变量路由到/people 并且返回内容的类型特别是APPLICATION_JSON MediaType静态字段使用accept方法。 如果使用其他路径则不会处理。 如果路径正确但是Accept标头不是接受的类型之一则请求将失败。 在继续之前我想contentType一下accept和contentType方法。 这两个设置的请求标头都accept对Accept标头的匹配将contentType匹配到Content-Type。 Accept标头定义了响应可接受的媒体类型因为我们返回的Person对象的JSON表示将其设置为APPLICATION_JSON 实际标头中为application/json 。 Content-Type具有相同的想法但是描述的是发送的请求正文中的媒体类型。 这就是为什么只有POST和PUT动词包含contentType的原因其他动词的主体中没有任何动词。 DELETE不包含accept和contentType因此我们可以得出结论既不希望返回任何内容也不希望在其请求正文中包含任何内容。 现在我们知道了如何设置路由让我们看一下编写处理传入请求的处理程序方法。 以下是处理来自先前示例中定义的路由的所有请求的代码。 Component
public class PersonHandler {private final PersonManager personManager;public PersonHandler(PersonManager personManager) {this.personManager personManager;}public MonoServerResponse get(ServerRequest request) {final UUID id UUID.fromString(request.pathVariable(id));final MonoPerson person personManager.findById(id);return person.flatMap(p - ok().contentType(APPLICATION_JSON).body(fromPublisher(person, Person.class))).switchIfEmpty(notFound().build());}public MonoServerResponse all(ServerRequest request) {return ok().contentType(APPLICATION_JSON).body(fromPublisher(personManager.findAll(), Person.class));}public MonoServerResponse put(ServerRequest request) {final UUID id UUID.fromString(request.pathVariable(id));final MonoPerson 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 MonoServerResponse post(ServerRequest request) {final MonoPerson 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 MonoServerResponse 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 MonoServerResponse getByCountry(ServerRequest serverRequest) {final String country serverRequest.pathVariable(country);return ok().contentType(APPLICATION_JSON).body(fromPublisher(personManager.findAllByCountry(country), Person.class));}
} 值得注意的一件事是缺少注释。 PersonHandler Component注释自动创建PersonHandler bean没有其他Spring注释。 我试图将大多数存储库逻辑都排除在此类之外并通过委派给它所包含的PersonRepository的PersonManager来隐藏对实体对象的任何引用。 如果您对PersonManager中的代码感兴趣那么可以在我的GitHub上看到它本文将不再对此进行进一步的解释因此我们可以专注于WebFlux本身。 好的回到手头的代码。 让我们仔细看看get和post方法以了解发生了什么。 public MonoServerResponse get(ServerRequest request) {final UUID id UUID.fromString(request.pathVariable(id));final MonoPerson person personManager.findById(id);return person.flatMap(p - ok().contentType(APPLICATION_JSON).body(fromPublisher(person, Person.class))).switchIfEmpty(notFound().build());
} 此方法用于从支持此示例应用程序的数据库中检索一条记录。 由于Cassandra是首选数据库因此我决定对每个记录的主键使用UUID 这具有使测试示例更加烦人的不幸效果但是有些复制和粘贴无法解决。 请记住此GET请求的路径中包含一个路径变量。 在传递给该方法的ServerRequest上使用pathVariable方法我们可以通过提供变量名称在本例中为id来提取其值。 然后将ID转换为UUID 如果字符串的格式不正确则将引发异常我决定忽略此问题以使示例代码不会更混乱。 获得ID后我们可以查询数据库中是否存在匹配记录。 返回一个MonoPerson 它包含映射到Person的现有记录或者保留为空的Mono 。 使用返回的Mono我们可以根据它的存在输出不同的响应。 这意味着我们可以将有用的状态代码与主体内容一起返回给客户端。 如果记录存在则flatMap返回状态为OK的ServerResponse 。 除了此状态外我们还希望输出记录为此我们指定主体的内容类型在本例中为APPLICATION_JSON 然后将记录添加到其中。 fromPublisher将我们的MonoPerson 它是Publisher 与Person类一起使用因此它知道它映射到主体中的内容。 fromPublisher是BodyInserters类中的静态方法。 如果该记录不存在那么该流程将移至switchIfEmpty块并返回NOT FOUND状态。 由于找不到任何内容因此可以将主体保留为空因此我们只需要创建其中的ServerResponse 。 现在进入post处理程序。 public MonoServerResponse post(ServerRequest request) {final MonoPerson 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));
} 即使只是从第一行开始我们也可以看到它与get方法的工作方式已经不同。 由于这是一个POST请求因此需要从请求的主体中接受我们要保留的对象。 当我们尝试插入单个记录时我们将使用请求的bodyToMono方法从正文中检索Person 。 如果要处理多个记录则可能要使用bodyToFlux 。 我们将使用created方法返回CREATED状态该方法采用URI来确定插入记录的路径。 然后通过使用fromPublisher方法将新记录添加到响应的正文中从而遵循与get方法类似的设置。 构成Publisher的代码略有不同但是输出仍然是MonoPerson 这很重要。 只是为了进一步说明如何完成插入使用我们生成的UUID将请求中传入的Person映射到新的Person 然后通过调用flatMap save其传递到save 。 通过创建一个新的Person我们仅将值插入我们允许的Cassandra中在这种情况下我们不希望UUID从请求主体传入。 因此关于处理程序就是这样。 显然还有其他一些我们没有经历过的方法。 它们的工作方式不同但是都遵循相同的概念即返回ServerResponse 该ServerResponse包含一个适当的状态代码和正文中的记录如果需要。 现在我们已经编写了运行基本Spring WebFlux后端所需的所有代码。 剩下的就是将所有配置捆绑在一起这对于Spring Boot来说很容易。 SpringBootApplication
public class Application {public static void main(String args[]) {SpringApplication.run(Application.class);}
} 而不是在这里结束帖子我们可能应该研究如何实际使用代码。 Spring提供了WebClient类来处理请求而不会阻塞。 现在我们可以利用它作为测试应用程序的方法尽管这里也可以使用WebTestClient 。 创建响应式应用程序时将使用WebClient而不是阻塞RestTemplate 。 下面的代码调用了PersonHandler中定义的处理程序。 public class Client {private WebClient client WebClient.create(http://localhost:8080);public void doStuff() {// POSTfinal Person record new Person(UUID.randomUUID(), John, Doe, UK, 50);final MonoClientResponse 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()));// GETclient.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));// ALLclient.get().uri(/people).accept(APPLICATION_JSON).exchange().flatMapMany(response - response.bodyToFlux(Person.class)).subscribe(person - System.out.println(ALL: person));// PUTfinal 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()));// DELETEclient.delete().uri(/people/{id}, ec2212fc-669e-42ff-9c51-69782679c9fc).exchange().map(ClientResponse::statusCode).subscribe(status - System.out.println(DELETE: status));}
} 不要忘记在某个地方实例化Client 下面是一种不错的懒惰方式 SpringBootApplication
public class Application {public static void main(String args[]) {SpringApplication.run(Application.class);Client client new Client();client.doStuff();}
} 首先我们创建WebClient 。 private final WebClient client WebClient.create(http://localhost:8080); 一旦创建我们就可以开始使用它做事因此可以使用doStuff方法。 让我们分解一下发送到后端的POST请求。 final MonoClientResponse 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())); 我将此内容写下的略有不同因此您可以看到从发送请求返回了MonoClientResponse 。 exchange方法将HTTP请求发送到服务器。 然后无论何时到达都会处理该响应。 当然使用WebClient我们指定我们要使用post方法发送POST请求。 的URI 然后用所添加的uri的方法重载的方法这一个发生在一个String 但另一个接受一个URI 。 我不敢说此方法执行了该方法所要求的操作然后将正文内容与Accept标头一起添加。 最后我们通过调用exchange发送请求。 请注意 APPLICATION_JSON的媒体类型与POST路由器功能中定义的类型匹配。 如果我们要发送其他类型请说TEXT_PLAIN 则将收到404错误因为不存在与请求返回的请求相匹配的处理程序。 使用通过调用exchange返回的MonoClientResponse 我们可以将其内容映射到所需的输出。 在上面的示例中状态代码将打印到控制台。 如果我们回想一下PersonHandler的post方法请记住它只能返回“已创建”状态但是如果发送的请求与之不正确匹配则将打印出“未找到”。 让我们看一下其他请求之一。 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)); 这是我们的典型GET请求。 它看起来与我们刚经历的POST请求非常相似。 主要区别在于uri将请求和UUID的路径在这种情况下为String 都作为参数它将替换路径变量{id} 并且正文保留为空。 响应的处理方式也不同。 在此示例中它提取响应的正文并将其映射到MonoPerson并打印出来。 可以使用前面的POST示例完成此操作但是响应的状态代码在该场景中更为有用。 从稍微不同的角度来看我们可以使用cURL发出请求并查看响应的外观。 CURL -H Accept:application/json -i localhost:8080/peopleHTTP/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}
] 响应看起来像这样显然它会根据您存储的数据而有所不同。 注意响应头。 transfer-encoding: chunked
Content-Type: application/json 此处的transfer-encoding表示可分块传输的数据可用于传输数据。 这就是我们所需要的以便客户端可以对返回的数据做出反应。 我认为这应该是一个停止的好地方。 我们在这里介绍了很多资料希望可以帮助您更好地理解Spring WebFlux。 我还想介绍WebFlux的其他一些主题但是我将在单独的文章中介绍这些主题因为我认为这足够长了。 总之在本文中我们非常简要地讨论了为什么要在典型的Spring MVC后端上使用Spring WebFlux。 然后我们研究了如何设置路由和处理程序以处理传入的请求。 处理程序实现了可以处理大多数REST动词的方法并在响应中返回了正确的数据和状态代码。 最后我们研究了两种向后端发出请求的方法一种是使用WebClient直接在客户端上处理输出另一种是通过cURL查看返回的JSON的外观。 如果您有兴趣查看我用来创建本文示例应用程序的其余代码可以在我的GitHub上找到它。 与往常一样如果您发现此帖子有帮助请分享它如果您想了解我的最新帖子则可以通过Twitter LankyDanDev关注我。 翻译自: https://www.javacodegeeks.com/2018/03/doing-stuff-with-spring-webflux.html