当前位置: 首页 > 后端技术 > Java

SpringMVC解析(四)程序化路由

时间:2023-04-01 13:27:46 Java

大多数情况下,我们在使用Spring的Controller时,都会使用@RequestMapping根据URL将请求路由到指定的方法。Spring也提供了一种编程方法来实现请求与路由方法之间的路由关系。这种关系在Spring启动时就确定了,在运行过程中是不可变的。编程式路由和注解式路由可以使用同一个DispatcherServlet。本文将介绍SpringprogrammaticEndpoint。本文主要参考Spring官方文档。概述在SpringMVC编程路由中,请求将由处理方法处理。处理方法在Spring中用HandlerFunction表示。该函数的入参为ServerRequest,返回值为ServerResponse。Spring可以通过编程方式定义路由规则RouterFunction,RouterFunction相当于@RequestMapping注解。我们可以如下配置路由规则,通过@Configuration中的@Bean在Servlet中注册路由规则RouterFunction。导入静态org.springframework.http.MediaType.APPLICATION_JSON;导入静态org.springframework.web.servlet.function.RequestPredicates.*;导入静态org.springframework.web.servlet.function.RouterFunctions.route;PersonRepository存储库=...PersonH??andlerhandler=newPersonH??andler(repository);RouterFunctionroute=route().GET("/person/{id}",accept(APPLICATION_JSON),handler::getPerson).GET("/person",accept(APPLICATION_JSON),handler::listPeople).POST("/person",handler::createPerson).build();publicclassPersonH??andler{//...publicServerResponselistPeople(ServerRequestrequest){//...}publicServerResponsecreatePerson(ServerRequestrequest){//...}publicServerResponsegetPerson(ServerRequestrequest){//...}}处理函数的定义在程序化路由中,一个请求必须交给一个处理函数来处理。这是处理函数。该函数的入参为ServerRequest和ServerResponse,分别与请求的Request和Response绑定,包括请求的header、body、状态码等信息。ServerRequestServerRequest包含请求中的所有信息,如请求方法、请求URL、请求Header和请求参数,并提供与请求体相关的访问方法。如果请求体是String类型的数据,我们可以通过下面的例子获取请求体数据:Stringstring=request.body(String.class);如果需要将request转化为对应的Bean,比如List,Spring会将Json或者xml数据反序列化为对应的对象:Listpeople=request.body(newParameterizedTypeReference>(){});我们可以通过如下方式获取请求中的参数信息:MultiValueMapparams=request.params();ServerResponseServerResponse用于向response中写入数据,通过builder模式可以生成相应的response。以下示例将返回响应为200的Json数据:Personperson=...ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);以下示例可以生成状态代码为201的已创建响应:URIlocation=...ServerResponse.created(location).build();return数据也可以是异步结果:Monoperson=webClient.get().retrieve().bodyToMono(Person.class);ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);Spring甚至允许headers和状态码是异步结果,p.name()).body(p));ServerResponse.async(asyncResponse);Spring也支持Server-SentEvents(类似于WebSocket),使用下面的例子:publicRouterFunctionsse(){returnroute(GET("/sse"),request->ServerResponse.sse(sseBuilder->{//将sseBuilder对象保存在某处..}));}//在其他线程中,发送一个StringsseBuilder.send("Helloworld");//或者一个对象,将转化为JSONPersonperson=...sseBuilder.send(person);//使用其他方法自定义事件sseBuilder.id("42").event("sseevent").data(person);//并在某些点完成seBuilder.complete();处理类的定义和处理方法可以用Lambda来表示,但是如果处理方法很多或者处理方法有共享状态,继续用Lambda的话,程序就会乱这样的话,就可以将这些类按照功能封装成不同的类,如下例所示:importstaticorg.springframework.http.MediaType.APPLICATION_JSON;导入静态org.springframework.web.reactive.function.server.ServerResponse。好的;公共类PersonH??andler{privatefinalPersonRepository存储库;publicPersonH??andler(PersonRepositoryrepository){this.repository=repository;}publicServerResponselistPeople(ServerRequestrequest){Listpeople=repository.allPeople().returnok()contentType(APPLICATION_JSON).body(people);}publicServerResponsecreatePerson(ServerRequestrequest)throwsException{Personperson=request.body(Person.class);repository.savePerson(person);返回ok().build();publicServerResponsegetPerson(ServerRequestrequest){intpersonId=Integer.parseInt(request.pathVariable("id"));Personperson=repository.getPerson(personId);如果(人!=null){returnok().contentType(APPLICATION_JSON).body(person);}else{返回ServerResponse.notFound().build();}}}参数校验如果需要校验请求中的参数,我们需要通过编程来校验。验证示例如下所示。验证完成后,返回验证结果。用户可根据验证结果自定义处理逻辑publicclassPersonH??andler{privatefinalValidatorvalidator=newPersonValidator();//...publicServerResponsecreatePerson(ServerRequestrequest){Personperson=request.body(Person.class);验证(人);repository.savePerson(person);返回ok().build();}privatevoidvalidate(Personperson){错误errors=newBeanPropertyBindingResult(person,"person");validator.validate(人,错误);如果(errors.hasErrors()){thrownewServerWebInputException(errors.toString());}}}路由函数的定义路由函数的作用是将请求绑定到相应的处理方法上。Spring提供了RouterFunctions工具来创建构建器模式的路由规则。构建器模式以RouterFunctions.route(RequestPredicate,HandlerFunction)的格式创建路由函数。RouterFunctionroute=RouterFunctions.route().GET("/hello-world",accept(MediaType.TEXT_PLAIN),request->ServerResponse.ok().body("HelloWorld")).build();PredicatesSpringMVC中的RequestPredicate用于判断一个请求是否会命中指定的规则。用户可以自定义RequestPredicate的实现,也可以使用RequestPredicates中的工具类来构建RequestPredicate。下面的例子使用工具类来满足GET方法,参数类型为MediaType.TEXT_PLAIN数据。RequestPredicate提供了常用的请求方法、请求头等RequestPredicates,也支持RequestPredicates之间的ANDor关系。RouterFunctionroute=RouterFunctions.route().GET("/hello-world",accept(MediaType.TEXT_PLAIN),request->ServerResponse.ok().body("HelloWorld")).build();路由规则我们可以在DistpatcherServlet中注册多个RouterFunction。这些RouterFunction之间应该有一个顺序。每个RouteFunction允许定义多个路由规则。这些路由规则是有序的。如果请求匹配了前面的路由规则,则不会继续匹配后面的路由规则,直接使用第一个匹配的规则。importstaticorg.springframework.http.MediaType.APPLICATION_JSON;importstaticorg.springframework.web.servlet.function.RequestPredicates.*;PersonRepositoryrepository=...PersonH??andlerhandler=newPersonH??andler(repository);RouterFunctionotherRoute=...RouterFunctionroute=route().GET("/person/{id}",accept(APPLICATION_JSON),handler::getPerson).GET("/person",accept(APPLICATION_JSON),handler::listPeople).POST("/person",handler::createPerson).add(otherRoute).build();嵌套路由如果一系列路由规则包含相同的条件,比如URL前缀相同,那么在这种情况下推荐使用嵌套路由,嵌套路由的用法如下:RouterFunctionroute=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@EnableMvcpublicclassWebConfigimplementsWebMvcConfigurer{@BeanpublicRouterFunctionrouterFunctionA(){//...}@BeanpublicRouterFunctionrouterFunctionB(){//...}//...@OverridepublicvoidconfigureMessageConverters(List>converters){//配置消息转换...}@OverridepublicvoidaddCorsMappings(CorsRegistryregistry){//配置CORS...}@OverridepublicvoidconfigureViewResolvers(ViewResolverRegistryregistry){//配置HTML呈现的视图分辨率。..}}路由过滤器在定义路由规则的时候,我们可以给指定的规则添加预执行方法、后执行方法和过滤器。我们还可以在ControllerAdvice中添加全局的预执行方法、后执行方法和过滤规则,所有的程序化路由规则都会用到这些方法。以下是使用预执行方法、后执行方法和过滤器的示例:RouterFunctionroute=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();SecurityManagersecurityManager=...RouterFunctionroute=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())){returnnext.handle(request);}else{returnServerResponse.status(UNAUTHORIZED).build();}})。建造();本文首发于微信公众号,版权所有,禁止转载!