少数状况下,咱们在应用Spring的Controller时,会应用@RequestMapping的模式把申请依照URL路由到指定办法上。Spring还提供了一种编程的形式去实现申请和路由办法之间的路由关系,这种关系在Spring启动时确定,运行过程中不可变。编程式路由和注解式路由能够应用同一个DispatcherServlet。本文会对Spring编程式Endpoint进行介绍,本文次要参考了Spring官网文档。
总览
在Spring MVC编程式路由中一次申请会被一个解决办法进行解决,解决办法在Spring中用HandlerFunction示意,函数的入参为ServerRequest,返回值为ServerResponse。Spring能够通过编程的形式定义路由规定RouterFunction,RouterFunction等价于@RequestMapping注解。咱们能够依照如下形式去配置路由规定,并且能够通过@Configuration中的@Bean来将路由规定RouterFunction注册到Servlet中。
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.RequestPredicates.*;
import static org.springframework.web.servlet.function.RouterFunctions.route;
PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);
RouterFunction<ServerResponse> route = route()
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson)
.build();
public class PersonHandler {
// ...
public ServerResponse listPeople(ServerRequest request) {
// ...
}
public ServerResponse createPerson(ServerRequest request) {
// ...
}
public ServerResponse getPerson(ServerRequest request) {
// ...
}
}
处理函数的定义
在编程式路由中,一个申请最终要交给一个处理函数去解决,这就是HandlerFunction。这个函数的入参是ServerRequest和ServerResponse,别离绑定了申请的Request和Response,并且蕴含了申请的header、Body、状态码等信息。
ServerRequest
ServerRequest蕴含了申请中的所有信息,如申请形式、申请URL、申请的Header和申请参数等信息,并且提供了申请体相干的拜访办法。
-
如果申请体是String类型的数据,咱们能够通过如下示例获取申请体数据:
String string = request.body(String.class);
-
如果须要把申请转为对应的Bean,如List<Person>,Spring会把Json或xml数据反序列化为对应的对象:
List<Person> people = request.body(new ParameterizedTypeReference<List<Person>>() {});
-
咱们能够通过如下形式获取申请中的参数信息:
MultiValueMap<String, String> params = request.params();
ServerResponse
ServerResponse用于向响应中写入数据,能够通过建造者模式生成对应的响应,
-
如下例子会返回响应为200的Json数据:
Person person = ... ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
-
如下的例子能够生成一个Created的响应,状态码是201:
URI location = ... ServerResponse.created(location).build();
-
返回的数据也能够是异步的后果:
Mono<Person> person = webClient.get().retrieve().bodyToMono(Person.class); ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
-
Spring甚至容许Header和状态码也是异步的后果
Mono<ServerResponse> asyncResponse = webClient.get().retrieve().bodyToMono(Person.class).map(p -> ServerResponse.ok().header("Name", p.name()).body(p)); ServerResponse.async(asyncResponse);
-
Spring还反对Server-Sent Events(和WebSocket相似),应用办法如下示例:
public RouterFunction<ServerResponse> sse() { return route(GET("/sse"), request -> ServerResponse.sse(sseBuilder -> { // Save the sseBuilder object somewhere.. })); } // In some other thread, sending a String sseBuilder.send("Hello world"); // Or an object, which will be transformed into JSON Person person = ... sseBuilder.send(person); // Customize the event by using the other methods sseBuilder.id("42") .event("sse event") .data(person); // and done at some point sseBuilder.complete();
解决类的定义
解决办法能够用Lambda来示意,然而如果解决办法很多或者解决办法有共享的状态,如果持续应用Lambda就会使程序很乱。这种状况下能够依照性能把这些类封装到不必的类中,示例如下所示:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
public class PersonHandler {
private final PersonRepository repository;
public PersonHandler(PersonRepository repository) {
this.repository = repository;
}
public ServerResponse listPeople(ServerRequest request) {
List<Person> people = repository.allPeople();
return ok().contentType(APPLICATION_JSON).body(people);
}
public ServerResponse createPerson(ServerRequest request) throws Exception {
Person person = request.body(Person.class);
repository.savePerson(person);
return ok().build();
}
public ServerResponse getPerson(ServerRequest request) {
int personId = Integer.parseInt(request.pathVariable("id"));
Person person = repository.getPerson(personId);
if (person != null) {
return ok().contentType(APPLICATION_JSON).body(person);
}
else {
return ServerResponse.notFound().build();
}
}
}
参数校验
如果须要对申请中的参数进行校验,咱们就须要通过编程的形式进行校验了,校验的示例如下所示,校验完结会返回校验后果,用户能够依据校验后果自定义解决逻辑。
public class PersonHandler {
private final Validator validator = new PersonValidator();
// ...
public ServerResponse createPerson(ServerRequest request) {
Person person = request.body(Person.class);
validate(person);
repository.savePerson(person);
return ok().build();
}
private void validate(Person person) {
Errors errors = new BeanPropertyBindingResult(person, "person");
validator.validate(person, errors);
if (errors.hasErrors()) {
throw new ServerWebInputException(errors.toString());
}
}
}
路由函数的定义
路由函数的作用是把申请绑定到对应的解决办法之上,Spring提供了RouterFunctions工具以建造者模式的办法创立路由规定,建造者模式创立以RouterFunctions.route(RequestPredicate, HandlerFunction)
格局创立路由函数。
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
request -> ServerResponse.ok().body("Hello World")).build();
Predicates
SpringMVC中的RequestPredicate用于判断一次申请是否会命中指定的规定,用户能够自定义RequestPredicate的实现,也能够应用RequestPredicates中的工具类去构建RequestPredicate,上面的例子通过工具类满足GET办法
和参数类型为MediaType.TEXT_PLAIN
的数据。RequestPredicatest提供了申请办法、申请头等罕用的RequestPredicate,RequestPredicate之间还反对与或关系。
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
request -> ServerResponse.ok().body("Hello World")).build();
路由规定
咱们能够向DistpatcherServlet中注册多个RouterFunction,这些RouterFunction之间应该有程序,每个RouteFunction又容许定义多个路由规定,这些路由规定之间是有程序的。如果申请匹配到了后面的路由规定匹配,那么它就不会再持续匹配前面的路由规定,会间接应用第一个匹配到的规定。
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.RequestPredicates.*;
PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);
RouterFunction<ServerResponse> otherRoute = ...
RouterFunction<ServerResponse> route = route()
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson)
.add(otherRoute)
.build();
嵌套路由
如果一系列路由规定蕴含了雷同的条件,比方雷同前缀的URL等,这种条件下举荐应用嵌套路由,嵌套路由的应用办法如下所示:
RouterFunction<ServerResponse> route = route()
.path("/person", builder -> builder
.GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET(accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson))
.build();
路由配置
上文中介绍了如何定义路由规定,定义好的路由规定往往须要注册到Spring的容器中,咱们能够通过实现WebMvcConfigurer接口向容器中增加配置信息,并且依据配置信息生成DispatcherServlet。
@Configuration
@EnableMvc
public class WebConfig implements WebMvcConfigurer {
@Bean
public RouterFunction<?> routerFunctionA() {
// ...
}
@Bean
public RouterFunction<?> routerFunctionB() {
// ...
}
// ...
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// configure message conversion...
}
@Override
public void addCorsMappings(CorsRegistry registry) {
// configure CORS...
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// configure view resolution for HTML rendering...
}
}
路由过滤器
在定义一条路由规定的时候,咱们能够对指定规定增加执行前办法、执行后办法和过滤器。咱们也能够再ControllerAdvice中增加全局的执行前办法、执行后办法和过滤器规定,所有的编程式路由规定都会应用这些办法。如下为执行前办法、执行后办法和过滤器的应用示例:
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople)
.before(request -> ServerRequest.from(request)
.header("X-RequestHeader", "Value")
.build()))
.POST("/person", handler::createPerson))
.after((request, response) -> logResponse(response))
.build();
SecurityManager securityManager = ...
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople))
.POST("/person", handler::createPerson))
.filter((request, next) -> {
if (securityManager.allowAccessTo(request.path())) {
return next.handle(request);
}
else {
return ServerResponse.status(UNAUTHORIZED).build();
}
})
.build();
本文最先公布至微信公众号,版权所有,禁止转载!
发表回复