原创 Springboot实战案例锦集 Spring全家桶实战案例源码 2024年08月08日 08:02 新疆
![](https://img-blog.csdnimg.cn/img_convert/07b25266849bab6872e4c2d5b3a628d2.png)
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) ;
可以使用异步结果作为主体,其形式可以是 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是请求处理的核心类,该类是个函数式接口,如下:
@FunctionalInterface
public interface HandlerFunction<T extends ServerResponse> {
T handle(ServerRequest request) throws Exception ;
}
这样我们可以直接通过lambda的方式定义该接口的实现。如下示例:
HandlerFunction<ServerResponse> handler =
request -> ServerResponse.ok().body("Hello Pack") ;
开发一个模块下来怎么都的有好多个处理请求的方法,如果没有都入上面那么写代码的可读性将比较的差,所以我们可以将这些方法都写到一个类中,就像是编写基于注解的Controller类一样,如下示例:
@Component
public 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() ;
}
}
}
如上,将所有的业务操作方法都定义在一个类中,接下来通过函数引用的方式使用
@Configuration
public 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通过该类对请求参数进行验证,如下示例:
@Component
public 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
}
}
对应于函数式接口的嵌套路由
@Bean
public 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实现类似功能,如下示例:
@Bean
RouterFunction<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接口参数这样处理太优雅了