在响应式的WebFlux出现以前,我们可以使用Spring MVC来编写Rest API,但是传统的基于Servlet的Web框架,在本质上都是阻塞和多线程的,每个连接都会使用一个线程,使用的是线程池中拉取的Worker线程,这里涉及到线程状态的切换,会耗时,且请求线程是阻塞的,直到Worker线程处理完为止。

Spring MVC编写Rest API

先回忆一下Spring MVC来编写Rest API的例子,这里使用《Spring实战(第五版)》中的示例代码:

@RestController
@RequestMapping(path="/design",                
                produces="application/json")
@CrossOrigin(origins="*")   
public class DesignTacoController {
  private TacoRepository tacoRepo;
  
  @Autowired
  EntityLinks entityLinks;

  public DesignTacoController(TacoRepository tacoRepo) {
    this.tacoRepo = tacoRepo;
  }

  @GetMapping("/recent")
  public Iterable<Taco> recentTacos() {         
    PageRequest page = PageRequest.of(
            0, 12, Sort.by("createdAt").descending());
    return tacoRepo.findAll(page).getContent();
  }

  @PostMapping(consumes="application/json")
  @ResponseStatus(HttpStatus.CREATED)
  public Taco postTaco(@RequestBody Taco taco) {
    return tacoRepo.save(taco);
  }
  //end::postTaco[]
  
  
  @GetMapping("/{id}")
  public Taco tacoById(@PathVariable("id") Long id) {
    Optional<Taco> optTaco = tacoRepo.findById(id);
    if (optTaco.isPresent()) {
      return optTaco.get();
    }
    return null;
  }
 }

关于几个注解的说明:

@RestController

它是一个类似于@Controller和@Service的构造型注解,能够让类被组件扫描功能发现,且@RestController告诉Spring,控制器中的所有处理器方法的返回值都要直接写入响应体中,这样就不要为每个处理器方法再添加@ResponseBody注解


@CrossOrigin

@CrossOrigin允许来自任何域的客户端消费该API

Spring WebFlux编写响应式Rest API

异步的Web框架能够以更少的线程获得更高的可扩展性,可以减少线程管理的开销。通过使用所谓的事件轮询(event looping)机制,这些框架能够用一个线程处理很多请求,这样每次连接的成本很低。
异步Web框架借助事件轮询机制能够以更少的线程处理更多的请求

Spring 5引入了一个非阻塞、异步的Web框架,该框架在很大程度上是基于Reactor项目的,能够解决Web应用和API中对更好的可扩展性的需求。
Spring 5通过名为WebFlux的新Web框架来支持反应式Web应用

在上图中,Spring MVC是建立在Java Servlet API之上,因此需要Servlet容器才(比如Tomcat)能执行。而WebFlux并不会绑定Servlet API,它构建在Reactive HTTP API之上(其实与Servlet API功能相同,只是采取了响应式的方式),因此WebFlux可以运行在任意非阻塞的Web容器中,包括Netty、Tomcat、Jetty等任意Servlet 3.1及以上的容器。

WebFlux的默认嵌入式服务器是Netty而不是Tomcat。Netty是一个异步、事件驱动的服务器,非常适合Spring WebFlux这样的响应式Web框架。

Spring WebFlux的控制器方法要接受和返回响应式类型,如Reactor中的Mono和Flux,而不是领域类型和集合。Spring WebFlux也能处理RxJava类型,如Observable、Single和Completable。

它应该成为完整的端到端反应式栈的一部分(为了最大化反应式Web框架的收益)

示例代码如下:

@RestController
@RequestMapping(path = "/design", produces = "application/json")
@CrossOrigin(origins = "*")
public class DesignTacoController {
  private TacoRepository tacoRepo;

  public DesignTacoController(TacoRepository tacoRepo) {
    this.tacoRepo = tacoRepo;
  }

  @GetMapping("/recent")
  public Flux<Taco> recentTacos() {
    return tacoRepo.findAll().take(12);
  }

  @PostMapping(consumes = "application/json")
  @ResponseStatus(HttpStatus.CREATED)
  public Mono<Taco> postTaco(@RequestBody Taco taco) {
    return tacoRepo.save(taco);
  }

  @GetMapping("/{id}")
  public Mono<Taco> tacoById(@PathVariable("id") UUID id) {
    return tacoRepo.findById(id);
  }

}

尽管我们从repository得到了Flux<XXX>,但是直接返回后,由框架为我们调用subscribe(),所以在请求到来时,相应的方法会在数据真正从数据库取出之前就能立即返回。

Spring 5中的函数式模型编写响应式API

上面的代码仍然是使用的注解式WebFlux,虽然注解式很流行,但是仍有一些缺点。比如:

  • 所有基于注解的编程方式都会存在注解该做什么以及注解如何做之间的割裂
    注解本身定义了该做什么,但是具体如何做则是在框架代码的其他部分定义的,如果要进行自定义或者扩展,编程模型就会变得很麻烦,因为这样做需要修改注解之外的代码。
  • 代码调试比较麻烦,因为无法在注解上设置断点。

Spring 5中新的函数式编程模型像是一个库,而不是一个框架,能让我们在不使用注解的情况下,将请求映射到处理器代码中。

使用Spring的函数式编程模型在编写API会涉及4个主要的类型:

  • RequestPredicate :声明要处理的请求类型
  • RouterFunction : 声明如何将请求路由到处理器代码中
  • ServerRequest : 代表一个Http请求,包括对请求头和请求体的访问
  • ServerResponse : 代表一个Http响应,包括响应头和响应体信息

一个代码示例:

@Configuration
public class RouterFunctionConfig {

   @Autowired
   private TacaoRepository tacoRepo;

   @Bean
   public RouterFunction<?> routerFunction() {
     return route(GET("/design/taco"),this::recents)
          .andRoute(POST("/design"),this::postTaco); 
   }

    public Mono<ServerResponse> recents(ServerRequest request) {
     return ServerResponse.ok()
          .body(tacoPepo.findAll().take(12),Taco.class);
   }

    public Mono<ServerResponse> postTaco(ServerRequest request) {
     Mono<Taco> taco = request.bodyToMono(Taco.class);
     Mono<Taco> savedTaco = tacoPepo.save(taco);
     return ServerResponse.created(URI.created("http://localhost:8080/design/taco/" + 
            savedTaco.getId()))
            .body(savedTaco,Taco.class);
   }

}

2020/7/7 补充:一个博主写的关于响应式事务和数据库的博文:https://www.jdon.com/tags/39413

如果觉得我的文章对你有用,请随意赞赏