在一个配置类中,注册一个返回 RouterFunction
的 bean 即可。如下:
import static org.springframework.web.servlet.function.RouterFunctions.*;
import static org.springframework.web.servlet.function.ServerResponse.*;
@Configuration
public class Routes {
@Bean
public RouterFunction<ServerResponse> demoRoutes() {
return route()
.GET("/hello-world", req -> ok().body(Map.of("Hello", "world")))
.build();
}
}
你可以注册多个返回 RouterFunction
的 bean,注意不要使其中的路由有冲突即可。
响应返回数据默认的 content type 是 application/json
。如果需要修改,可以参考下文如何下载文件部分。
如果请求带有请求体,默认按 application/json
来解析。如果需要指定不同的 content type,可以参考下文如何上传文件部分。
请直接查看示例:
@Bean
public RouterFunction<ServerResponse> demoRoutes() {
return route()
.GET("/hello-world", req -> ok().body(Map.of("Hello", "world")))
.path("/demo", next -> next
.GET("/hello-world", req -> ok().body("Hello world"))
)
.build();
}
通过 ServerRequest#params()
方法可以获取。例如:
.GET("/get-search-param", req -> {
var id = req.params().getFirst("id");
return ObjectUtils.isEmpty(id)
? badRequest().body(new Err(400, "id is empty"))
: ok().body(Map.of("id", id));
})
通过 ServerRequest#headers()
方法获取。
通过 ServerRequest#cookies()
方法获取。
通过 ServerRequest#pathVariable(String)
方法或者 ServerRequest#pathVariables()
方法获取。
通过 ServerRequest#body(Class<T>)
方法获取。例如:
.POST("/persons", req -> {
var payload = req.body(CreatePersonPayload.class);
return ok().body(new Person(UUID.randomUUID().toString(), payload.getName()));
})
通过 ServerRequest#multipartData()
方法获取 multipart form data 中的项,然后按需筛选即可。例如:
.POST("/upload-csv", // 1. path
accept(MediaType.MULTIPART_FORM_DATA), // 2. 要求必须通过 multipart/form-data 提交
req -> { // 3. request handler
var file = req.multipartData().getFirst("file");
if (file == null) {
return badRequest().body(new Err(400, "file is empty"));
}
var parser = new CSVParser(new InputStreamReader(file.getInputStream()), CSVFormat.DEFAULT);
return ok().body(parser.stream().map(CSVRecord::toList).toList());
})
把数据写入响应 body 中即可。例如:
.GET("/download/{fileName}.txt", req -> {
var fileName = req.pathVariable("fileName");
var text = "This is a text file named " + fileName + ".\nNobody knows xx better than me.";
var bytes = text.getBytes(StandardCharsets.UTF_8);
return ok().contentType(MediaType.TEXT_PLAIN) // 1. 指定文件的类型
.contentLength(bytes.length) // 2. 指定文件长度
.body(bytes); // 3. 写入数据到 response body
})
如果你不打算自己手写数据校验器的话,需要加上 spring-boot-starter-validation
依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
之后注入默认的 org.springframework.validation.Validator
即可。例如:
// 待校验数据
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class UpdatePersonPayload {
@NotEmpty(message = "Person name cannot be empty!")
@Size(min = 5, max = 20, message = "Person name length must be between {min} and {max}.")
private String name;
}
// router
@Bean
public RouterFunction<ServerResponse> demoRoutes(Validator validator) { // 注入 validator
return route()
.path("/demo", next -> next
.PUT("/persons/{id}", accept(MediaType.APPLICATION_JSON), request -> {
var id = request.pathVariable("id");
var payload = request.body(UpdatePersonPayload.class);
var errors = validator.validateObject(payload); // 使用 validator 检查
if (errors.hasErrors()) {
throw new AppException(400, errors.getAllErrors().getFirst().getDefaultMessage());
} else {
return ok().body(new Person(id, payload.getName()));
}
})
)
.build();
}
Spring MVC 中的 @ExceptionHandler
在此处失效。全局的错误处理方法是在最外层的 router 外面增加 filter。比如:
@Bean
public RouterFunction<ServerResponse> demoRoutes(Validator validator) {
return route()
.GET("/hello-world", req -> ok().body(Map.of("Hello", "world")))
.path("/demo", next -> next
.PUT("/persons/{id}", accept(MediaType.APPLICATION_JSON), request -> {
var id = request.pathVariable("id");
var payload = request.body(UpdatePersonPayload.class);
var errors = validator.validateObject(payload);
if (errors.hasErrors()) {
throw new AppException(400, errors.getAllErrors().getFirst().getDefaultMessage());
} else {
return ok().body(new Person(id, payload.getName()));
}
})
)
.build()
.filter((req, next) -> {
// 这里处理全局错误
try {
return next.handle(req);
} catch (AppException e) {
return status(e.getCode()).body(new Err(e.getCode(), e.getMessage()));
} catch (Exception e) {
var internalServerError = HttpStatus.INTERNAL_SERVER_ERROR;
return status(internalServerError).body(new Err(internalServerError.value(), e.getMessage()));
}
});
}
如果希望把错误处理放到更外层,可以使用 jakarta.servlet.Filter
,方式和上面类似。