关于java:SpringMVC-解析四编程式路由

43次阅读

共计 7463 个字符,预计需要花费 19 分钟才能阅读完成。

少数状况下,咱们在应用 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();

本文最先公布至微信公众号,版权所有,禁止转载!

正文完
 0