环境: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) ;
可以使用异步结果作为主体,其形式可以是 CompletableFuture、Publisher 或 ReactiveAdapterRegistry 支持的任何其他类型,如下示例:
Mono<Person> person = Mono.just(new Person(1L, "张三")) ;ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person) ;
还支持SSE(单向即时通信),关于SSE可以查看下面这篇文章
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 {@Beanpublic 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();}
在请求到来及返回时分别执行响应的动作。
以上是本篇文章的全部内容,如对你有帮助帮忙点赞+转发+收藏
推荐文章
强大!实时监控SpringBoot运行时状态及应用运行时信息(数据库, Redis,MQ等)
SpringBoot中Controller接口参数这样处理太优雅了
SpringBoot参数验证@Validated和@Valid分清楚了吗?这些验证细节你知道吗?
SpringBoot自带Controller接口监控,赶紧用起来



