Spring Boot 2.x实战87 - 响应式编程2 - Spring WebFlux(类WebMVC注解式控制器、函数式端点、WebFlux的配置)

2. Spring WebFlux

新建应用,信息如下:

Group:top.wisely

Artifact:learning-webflux

Dependencies:Spring Reactive WebLombok

build.gradle文件中的依赖如下:

dependencies {
   implementation 'org.springframework.boot:spring-boot-starter-webflux'
   compileOnly 'org.projectlombok:lombok'
   annotationProcessor 'org.projectlombok:lombok'
	//...
}

2.1 WebFlux基础

Spring WebFlux不依赖于Servlet API,它可以运行在非Servlet容器Netty、Undertow和任何Servlet 3.1+的Servlet容器(Tomcat,Jetty)之上。虽然Servlet 3.1提供了非阻碍I/O的API,但是有很多其它的API依然是同步或阻碍式的;这使得Spring需要重新构建完全基于异步和非阻碍式的运行环境。

Spring WebFlux最底层的组件是HttpHandler,它用来适配不同的服务器引擎(Netty、Undertow、Tomcat、Jetty)。

在通过HttpHandler消除了服务器引擎的异构行后,Spring WebFlux的API设计与Spring MVC是高度一致的。

Spring WebFlux与Spring MVC在概念上有对应的关系:

Spring WebFlux Spring Web MVC
DispatcherHandler DispatcherServlet
WebFilter Filter
HttpMessageWriter
HttpMessageReader
HttpMessageConverter
HandlerMapping HandlerMapping
HandlerAdapter HandlerAdapter
ServerHttpRequest
ServerHttpResponse
ServletRequest
ServletResponse

Spring WebFlux支持两种编程模型:

  • 注解控制器:和Spring MVC使用的注解式保持一致。

    • RequestMappingHandlerMapping:映射请求与@RequestMapping控制器类和方法;
    • RequestMappingHandlerAdapter:调用@RequestMapping注解的方法。

    Sping MVC的两个类与这两个类名称一致,在不同的包里,有不同的实现,但功能保持一致。

  • 函数式端点:基于Lambada表达式、轻量级的函数式编程模型。

    • RouterFunctionMapping:用来支持RouterFunction
    • HandlerFunctionAdapter:用来支持HandlerFunctions

    从Spring 5.2或Spring 2.2.x以后Spring MVC也支持这种编程模型。

2.2 Spring Boot的自动配置

Spring Boot提供的自动配置主要有:

  • CodecsAutoConfiguration:Spring WebFlux使用HttpMessageReaderHttpMessageWriter接口来转换HTTP请求和返回。本配置类为我们注册了CodecCustomizer的Bean,默认使用Jackson2JsonEncoderJackson2JsonDecoder

  • ReactiveWebServerFactoryAutoConfiguration:为响应式Web服务器进行自动配置。

  • WebFluxAutoConfiguration:使用等同于@EnableWebFlux的配置开启WebFlux的支持。可通过WebFluxProperties使用spring.webflux.*来对WebFlux进行配置:

    spring:
      webflux:
        date-format: yyyy-MM-dd # 日期格式
        static-path-pattern: /resouces/static/** # 静态资源目录
    
  • WebClientAutoConfiguration:为WebClient进行自动配置。

2.2 注解控制器

2.2.1 示例

我们演示一个简单的例子,先定义简单的领域模型:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
    private Long id;
    private String name;
    private Integer age;
}
@RestController
@RequestMapping("/people")
public class PersonController {

    PersonRepository personRepository; //1

    public PersonController(PersonRepository personRepository) {
        this.personRepository = personRepository;
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Mono<Person> add(@RequestBody Person person){ //2
        return Mono.just(personRepository.save(person));
    }

    @GetMapping("/{id}")
    public ResponseEntity<Mono<Person>> getById(@PathVariable Long id){ //3
        return ResponseEntity.ok()
                .body(Mono.just(personRepository.findOne(id)));
    }
    @GetMapping
    public Flux<Person> list(){ //4
        return Flux.fromIterable(personRepository.list());
    }

    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public Mono<Void> delete(@PathVariable("id") Long id) { //5
        personRepository.delete(id);
        return Mono.empty();
    }
}
  1. 我们沿用“Spring Web MVC”章使用的PersonRepository(包含CommonRepositoryMyBeanUtils),注入到控制器中;

  2. 返回一个Person,使用Mono<Person>

  3. 我们使用ResponseEntity来构造返回,返回体为Mono<Person>

  4. 返回多个Person,使用Flux<Person>PersonRepository(包含CommonRepository)没有list方法,代码如下:

    @Override
    public Set<Person> list() {
        return people;
    }
    
  5. Mono<Void> 是响应式下的void

2.2.2 WebClient调用

WebClient是Spring WebFlux给我们提供的响应式的客户端,Spring Boot通过WebClientAutoConfiguration已为我们自动配置,本例为方便起见在当前应用直接编写客户端代码。

@Component
public class ControllerClient {

    WebClient webClient;

    public ControllerClient(WebClient.Builder builder) {
        this.webClient = builder.build(); //1 
    }

    public void add1(){
        System.out.println("添加第一条数据");
        Mono<Person> mono = webClient
                .post() //2
                .uri("http://localhost:8080/people") //3
                .body(Mono.just(new Person(1l, "wyf", 35)), Person.class) //4
                .retrieve() //5
                .bodyToMono(Person.class); //6
        mono.subscribe(System.out::println); //7
    }

    public void findOne(){
        System.out.println("查询一条数据");
        Mono<Person> mono = webClient
                .get()
                .uri("http://localhost:8080/people/{id}", 1l)//8
                .retrieve()
                .bodyToMono(Person.class);
        mono.subscribe(System.out::println);
    }

    public void add2(){
        System.out.println("添加第二条数据");
        Mono<Person> mono = webClient
                .post()
                .uri("http://localhost:8080/people")
                .body(Mono.just(new Person(2l, "foo", 34)), Person.class)
                .retrieve()
                .bodyToMono(Person.class);
        mono.subscribe(System.out::println);
    }


    public void list(){
        System.out.println("获取列表数据");
        Flux<Person> flux = webClient
                .get()
                .uri("http://localhost:8080/people")
                .retrieve()
                .bodyToFlux(Person.class); //9
        flux.subscribe(System.out::println);
    }

    public void delete(){
        System.out.println("删除一条数据");
        Mono<Void> mono = webClient
                .delete()
                .uri("http://localhost:8080/people/{id}", 1l)
                .retrieve()
                .bodyToMono(Void.class);
        mono.subscribe(System.out::println);
    }

}
  1. 使用Spring Boot为我们自动配置的WebClient.Builder Bean来构建WebClient

  2. 针对RESTful服务,WebClient提供了post()get()put()head()put()patch()方法来指定HTTP方法;

  3. 通过uri()指定服务端的路径;

  4. 可以用body()方法向后台传送请求体,而当前的请求体也是响应式的;我们还可以通过header()方法。

.header("headerName","headerValue")
  1. 使用retrieve()方法执行请求,并获取返回体;

  2. 将请求体转换为响应式类型Mono

  3. 通过subscribe()订阅Mono,只有订阅才能发送数据;

  4. 可以通过通过URI模板设置URI参数,如:

    .uri("/peopple?name={name}&age={age}", "bar", "33")
    
  5. 将请求体转换为响应式类型Flux

我们使用CommandLineRunner来执行上述的请求:

@Bean
CommandLineRunner webClientClr(ControllerClient controllerClient){
   return args -> {
      controllerClient.add1();
      Thread.sleep(1000);
      controllerClient.findOne();
      Thread.sleep(1000);
      controllerClient.add2();
      Thread.sleep(1000);
      controllerClient.list();
      Thread.sleep(1000);
      controllerClient.delete();
      Thread.sleep(1000);
      controllerClient.list();
   };
}

因为每一个请求都是异步请求的,不能确保哪个请求先执行完,所以我们每一个请求后都睡眠了1秒。

在这里插入图片描述

2.3 函数式端点

我们使用RouteFunction来进行请求和方法映射,使用HandlerFunction接口来定义控制器方法。

HandlerFunction是一个函数接口:

@FunctionalInterface
public interface HandlerFunction<T extends ServerResponse> {
   Mono<T> handle(ServerRequest request);

}

这意味着控制器方法的参数式ServerRequest,返回值时Mono<T>其中TServerResponse的实现类。符合这个定义即为一个HandlerFunction。我们首先定义我们的控制器方法:

@Component
public class PersonHandler {
    PersonRepository personRepository;

    public PersonHandler(PersonRepository personRepository) {
        this.personRepository = personRepository;
    }


    public Mono<ServerResponse> add(ServerRequest request){
      Mono<Person> personMono = request.bodyToMono(Person.class); //1 
      return personMono.flatMap(person -> ServerResponse
              .status(HttpStatus.CREATED)
              .body(Mono.just(personRepository.save(person)),Person.class)); //2
    }

    public Mono<ServerResponse> getById(ServerRequest request){
        Long id = Long.valueOf(request.pathVariable("id")); //3
        return ServerResponse
                .ok()
                .body(Mono.just(personRepository.findOne(id)), Person.class); //4

    }

    public Mono<ServerResponse> delete(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        personRepository.delete(id);
        return ServerResponse
                .noContent()
                .build(Mono.empty());
    }
}
  1. 通过bodyToMono方法可以从request对象中将体取出并转换为Mono
  2. 可以通过ServerResponse的静态方法来构造返回,我们的存储Repository是非响应式的,所以还需通过Mono.just转换一下;
  3. 可以通过request对象的pathVariable()方法获取路径变量;更多能从request中能获取的信息,请参照ServerRequest的API。

下面我们在配置类中对RouterFunction进行配置:

//...
import static org.springframework.web.reactive.function.server.RequestPredicates.*;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;

@Configuration
public class RoutingConfiguration {
    @Bean
    public RouterFunction<ServerResponse> personRouterFunction(PersonHandler personHandler,
                                                               PersonRepository personRepository){
        return route(POST("/people"), personHandler::add) //1
                .andRoute(GET("/people/{id}"), personHandler::getById)
                .andRoute(GET("/people"), serverRequest -> ServerResponse
                        .ok()
                        .body(Flux.fromIterable(personRepository.list()), Person.class))
                .andRoute(DELETE("/people/{id}"), personHandler::delete);

    }
}

我们使用RouterFunctions静态方法route()andRoute()来构建RouterFunction,方法接受两个参数:

  • RequestPredicate:可通过RequestPredicates中的静态方法来构造请求;
  • HandlerFunction:符合HandlerFunction接口声明的方法均可作为入参;我们既可以使用方法引用和Lambda表达式。

我们注释掉PersonController上的@RestController注解,使用WebClient检验函数式端点的响应式接口,效果和使用注解控制器方式一致。

2.4 Spring WebFlux的配置

我们若需要配置Spring WebFlux只需让配置配实现接口WebFluxConfigurer,这样我们既能保留Spring Boot给WebFlux配置又能添加我们的定制配置。若我们向完全控制WebFlux,则在配置类添加注解@EnableWebFlux

@Configuration
public class WebFluxConfig implements WebFluxConfigurer {
}

配置方式和Spring MVC类似,本节不做具体演示。

新书推荐:

我的新书《从企业级开发到云原生微服务:Spring Boot 实战》已出版,内容涵盖了丰富Spring Boot开发的相关知识
购买地址:https://item.jd.com/12760084.html
在这里插入图片描述

主要包含目录有:

第一章 初识Spring Boot(快速领略Spring Boot的美丽)
第二章 开发必备工具(对常用开发工具进行介绍:包含IntelliJ IDEA、Gradle、Lombok、Docker等)
第三章 函数式编程
第四章 Spring 5.x基础(以Spring 5.2.x为基础)
第五章 深入Spring Boot(以Spring Boot 2.2.x为基础)
第六章 Spring Web MVC
第七章 数据访问(包含Spring Data JPA、Spring Data Elasticsearch和数据缓存)
第八章 安全控制(包含Spring Security和OAuth2)
第九章 响应式编程(包含Project Reactor、Spring WebFlux、Reactive NoSQL、R2DBC、Reactive Spring Security)
第十章 事件驱动(包含JMS、RabbitMQ、Kafka、Websocket、RSocket)
第11章 系统集成和批处理(包含Spring Integration和Spring Batch)
第12章 Spring Cloud与微服务
第13章 Kubernetes与微服务(包含Kubernetes、Helm、Jenkins、Istio)
多谢大家支持。

猜你喜欢

转载自blog.csdn.net/wiselyman/article/details/107076309