优雅!SpringBoot通过函数式编程模型声明Restful API接口

原创 Springboot实战案例锦集 Spring全家桶实战案例源码 2024年08月08日 08:02 新疆

Spring全家桶实战案例源码

spring, springboot, springcloud 案例开发详解

543篇原创内容

公众号

环境:SpringBoot2.7.18



1. 简介

Spring Web MVC 包含了 WebMvc.fn,这是一个轻量级的函数式编程模型,其中使用函数来路由和处理请求,并且设计了不可变性的契约。

WebMvc.fn使用HandlerFunction处理HTTP请求,并通过RouterFunction将请求路由到相应的处理器,实现了一种基于函数式编程的轻量级模型,它是基于注解模型的替代方案,提供了更灵活的处理和路由机制。

接下来,将详细介绍这种基于函数式编程模型在实际应用当中的方方面面。

2. 核心元素

请求中通过HandlerFunction处理HTTP请求,而这就涉及到下面机构核心的类。

ServerRequest 和 ServerResponse 是不可变的接口,可让 JDK 8 方便地访问 HTTP 请求和响应,包括头部、主体、方法和状态代码。

ServerRequest

ServerRequest 提供对 HTTP 方法、URI、标头和查询参数的访问,而对正文的访问则通过 body 方法提供。

获取请求body

String string = request.body(String.class) ;

指定参数化类型

List<Person> people = request.body(new ParameterizedTypeReference<List<Person>>() {}) ;

访问请求参数

MultiValueMap<String, String> params = request.params() ;


ServerResponse

使用 ServerResponse 的构建器方法,可以方便地设置 HTTP 响应的状态、header和body(如 JSON 内容),因为它是不可变的,确保了响应的不可变性。如下示例:

Person person = new Person(1L, "Admin") ;ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person) ;

可以使用异步结果作为主体,其形式可以是 CompletableFuturePublisher 或 ReactiveAdapterRegistry 支持的任何其他类型,如下示例:

Mono<Person> person = Mono.just(new Person(1L, "张三")) ;ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person) ;

还支持SSE(单向即时通信),关于SSE可以查看下面这篇文章

实时数据推送并非只有WebSocket一种选择

public RouterFunction<ServerResponse> sse() {  return route(GET("/sse"), request -> ServerResponse.sse(sseBuilder -> {    // 将当前的sseBuilder保存下来  }));}// 在其它线程中发送信息sseBuilder.send("Hello world") ; // 也可以是对象Person person = new Person(1L, "李四") ;sseBuilder.send(person) ;// 完成&结束sseBuilder.complete() ;

接下来是核心的请求处理句柄类。

Handler Class请求处理句柄

在上文中已经说了HandlerFunction是请求处理的核心类,该类是个函数式接口,如下:

@FunctionalInterfacepublic interface HandlerFunction<T extends ServerResponse> {  T handle(ServerRequest request) throws Exception ;}

这样我们可以直接通过lambda的方式定义该接口的实现。如下示例:

HandlerFunction<ServerResponse> handler =   request -> ServerResponse.ok().body("Hello Pack") ;

开发一个模块下来怎么都的有好多个处理请求的方法,如果没有都入上面那么写代码的可读性将比较的差,所以我们可以将这些方法都写到一个类中,就像是编写基于注解的Controller类一样,如下示例:

@Componentpublic class PersonHandler {
  private final PersonRepository personRepository ;  public PersonHandler(PersonRepository personRepository) {    this.personRepository = personRepository ;  }  public ServerResponse listPeople(ServerRequest request) {     List<Person> people = personRepository.findAll() ;    return ok().contentType(APPLICATION_JSON).body(people) ;  }  public ServerResponse createPerson(ServerRequest request) {     Person person = request.body(Person.class) ;    personRepository.save(person) ;    return ok().build() ;  }  public ServerResponse getPerson(ServerRequest request) {     int id = Long.valueOf(request.pathVariable("id")) ;    Person person = personRepository.findById(id).orElse(null) ;    if (person != null) {        return ok().contentType(APPLICATION_JSON).body(person) ;    } else {        return ServerResponse.notFound().build() ;    }  }}

如上,将所有的业务操作方法都定义在一个类中,接下来通过函数引用的方式使用

@Configurationpublic class RouterConfig {  @Bean  public RouterFunction<ServerResponse> router(PersonHandler handler) {    return route()      .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)      .GET("/person", accept(APPLICATION_JSON), handler::listPeople)      .POST("/person", handler::createPerson)      .build();  }}

这样代码优雅多了。

参数验证

在请求处理的Handler类中,我们可以直接注入Validator通过该类对请求参数进行验证,如下示例:

@Componentpublic class PersonHandler {  private final PersonRepository personRepository ;  private final Validator validator ;  // ...  public ServerResponse createPerson(ServerRequest request) {     Person person = request.body(Person.class) ;    // 参数验证    Errors errors = new BeanPropertyBindingResult(person, "person") ;    this.validator.validate(person, errors) ;    // 如果有错误则抛出异常    if (errors.hasErrors()) {      throw new RuntimeException(errors.toString()) ;     }    personRepository.save(person) ;    return ok().build() ;  }  // Other}

以上是关于函数式编程模型的核心元素的介绍。

3. 高级用法

Predicates谓词

通过自定义 RequestPredicate 实现请求匹配,但 RequestPredicates 实用程序类提供了基于请求路径、HTTP 方法、内容类型等的常用实现,如下示例:

RouterFunction<ServerResponse> route = RouterFunctions.route()  .GET("/r1", accept(MediaType.TEXT_PLAIN),     request -> ServerResponse.ok().body("Hello Pack")).build() ;

该接口将只匹配Accpet header为text/plain的请求。

你还可以通过RequestPredicate.and(RequestPredicate)和RequestPredicate.or(RequestPredicate)进行更加复杂的谓词条件设置。

嵌套路由

嵌套路由与基于注解的Controller接口在类上使用@RequestMapping("/persons") 一样,也就是相当于统一加了一个前缀。

基于注解的Controller

@RestController@RequestMapping("/persons")public class PersonController {  @GetMapping("/{id}")  public Person queryById(@PathVariable("id") Long id) {    // TODO  }}

对应于函数式接口的嵌套路由

@Beanpublic RouterFunction<ServerResponse> person(PersonHandler handler) {  return route()    // 此处对应上面的@RequestMapping("/persons")    .path("/persons", builder -> builder        .GET("/{id}", accept(APPLICATION_JSON), handler::getPerson))        // 其它方法的请求    )        .build();}

在外层包一层/persons

请求过滤

你还可以使用路由函数生成器上的 before、after 或 filter 方法过滤处理程序函数。与基于注解Controller接口应用中的@ControllerAdvice、Servlet Filter实现类似功能,如下示例:

@BeanRouterFunction<ServerResponse> person() {    return route()    .path("/persons", b1 -> b1      .nest(RequestPredicates.all(), b2 -> b2        .GET("/{id}", accept(MediaType.TEXT_HTML), request -> ServerResponse.ok().body("query person"))        .before(request -> {          System.out.printf("请求Token: %s%n", request.headers().header("access-token")) ;          return request ;        })        .after((request, response) -> {          System.out.println("after ... ") ;          return response        }))      )    .build();}

在请求到来及返回时分别执行响应的动作。

以上是本篇文章的全部内容,如对你有帮助帮忙点赞+转发+收藏

推荐文章

10个SpringBoot开发技能,每个都很实用(一)

总结7种JVM出现OOM时的原因及解决方案

强大!实时监控SpringBoot运行时状态及应用运行时信息(数据库, Redis,MQ等)

SpringBoot @Value注解这些高级玩法用过吗?

SpringBoot高并发!业务方法重试就该使用它

Controller接口地址还能这样玩?

当心!请不要在SpringBoot中再犯这样严重的错误

SpringBoot中Controller接口参数这样处理太优雅了

Java开发人员必须掌握的11种干净代码最佳实践

一文让你彻底搞定Spring Security的基本使用

SpringBoot优雅地定制JSON响应数据

SpringBoot参数验证@Validated和@Valid分清楚了吗?这些验证细节你知道吗?

SpringBatch高阶应用:大数据批处理框架实战指南

一个注解完美实现分布式锁

SpringWeb开发中实用技巧

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值