【Spring 5】响应式Web框架实战(下)

2017年07月18日 01:23:07

引子:被誉为“中国大数据第一人”的涂子沛先生在其成名作《数据之巅》里提到,摩尔定律、社交媒体、数据挖掘是大数据的三大成因。IBM的研究称,整个人类文明所获得的全部数据中,有90%是过去两年内产生的。在此背景下,包括NoSQL,Hadoop, Spark, Storm, Kylin在内的大批新技术应运而生。其中以RxJavaReactor为代表的响应式(Reactive)编程技术针对的就是经典的大数据4V定义(Volume,Variety,Velocity,Value)中的Velocity,即高并发问题,而在即将发布的Spring 5中,也引入了响应式编程的支持。在接下来的几周,我会围绕响应式编程分三期与你分享我的一些学习心得。本篇是第三篇(下),通过一个简单的Spring 5示例应用,探一探即将于下月底发布的Spring 5的究竟。

前情概要:
- 【Spring 5】响应式Web框架前瞻
- 响应式编程总览
- 【Spring 5】响应式Web框架实战(上)

1 回顾

上篇介绍了如何使用Spring MVC注解实现一个响应式Web应用(以下简称RP应用),本篇接着介绍另一种实现方式——Router Functions。

2 实战

2.1 Router Functions

Router Functions是Spring 5新引入的一套Reactive风格(基于Flux和Mono)的函数式接口,主要包括RouterFunctionHandlerFunctionHandlerFilterFunction,分别对应Spring MVC中的@RequestMapping@ControllerHandlerInterceptor(或者Servlet规范中的Filter)。

和Router Functions搭配使用的是两个新的请求/响应模型,ServerRequestServerResponse,这两个模型同样提供了Reactive风格的接口。

2.2 示例代码

下面接着看我GitHub上的示例工程里的例子。

2.2.1 自定义RouterFunction和HandlerFilterFunction

@Configuration
public class RestaurantServer implements CommandLineRunner {

    @Autowired
    private RestaurantHandler restaurantHandler;

    /**
     * 注册自定义RouterFunction
     */
    @Bean
    public RouterFunction<ServerResponse> restaurantRouter() {
        RouterFunction<ServerResponse> router = route(GET("/reactive/restaurants").and(accept(APPLICATION_JSON_UTF8)), restaurantHandler::findAll)
                .andRoute(GET("/reactive/delay/restaurants").and(accept(APPLICATION_JSON_UTF8)), restaurantHandler::findAllDelay)
                .andRoute(GET("/reactive/restaurants/{id}").and(accept(APPLICATION_JSON_UTF8)), restaurantHandler::get)
                .andRoute(POST("/reactive/restaurants").and(accept(APPLICATION_JSON_UTF8)).and(contentType(APPLICATION_JSON_UTF8)), restaurantHandler::create)
                .andRoute(DELETE("/reactive/restaurants/{id}").and(accept(APPLICATION_JSON_UTF8)), restaurantHandler::delete)
                // 注册自定义HandlerFilterFunction
                .filter((request, next) -> {
                    if (HttpMethod.PUT.equals(request.method())) {
                        return ServerResponse.status(HttpStatus.BAD_REQUEST).build();
                    }
                    return next.handle(request);
                });
        return router;
    }

    @Override
    public void run(String... args) throws Exception {
        RouterFunction<ServerResponse> router = restaurantRouter();
        // 转化为通用的Reactive HttpHandler
        HttpHandler httpHandler = toHttpHandler(router);
        // 适配成Netty Server所需的Handler
        ReactorHttpHandlerAdapter httpAdapter = new ReactorHttpHandlerAdapter(httpHandler);
        // 创建Netty Server
        HttpServer server = HttpServer.create("localhost", 9090);
        // 注册Handler并启动Netty Server
        server.newHandler(httpAdapter).block();
    }
}

可以看到,使用Router Functions实现RP应用时,你需要自己创建和管理容器,也就是说Spring 5并没有针对Router Functions提供IoC支持,这是Router Functions和Spring MVC相比最大的不同。除此之外,你需要通过RouterFunction的API(而不是注解)来配置路由表和过滤器。对于简单的应用,这样做问题不大,但对于上规模的应用,就会导致两个问题:1)Router的定义越来越庞大;2)由于URI和Handler分开定义,路由表的维护成本越来越高。那为什么Spring 5会选择这种方式定义Router呢?接着往下看。

2.2.2 自定义HandlerFunction

@Component
public class RestaurantHandler {

    /**
     * 扩展ReactiveCrudRepository接口,提供基本的CRUD操作
     */
    private final RestaurantRepository restaurantRepository;

    /**
     * spring-boot-starter-data-mongodb-reactive提供的通用模板
     */
    private final ReactiveMongoTemplate reactiveMongoTemplate;

    public RestaurantHandler(RestaurantRepository restaurantRepository, ReactiveMongoTemplate reactiveMongoTemplate) {
        this.restaurantRepository = restaurantRepository;
        this.reactiveMongoTemplate = reactiveMongoTemplate;
    }

    public Mono<ServerResponse> findAll(ServerRequest request) {
        Flux<Restaurant> result = restaurantRepository.findAll();
        return ok().contentType(APPLICATION_JSON_UTF8).body(result, Restaurant.class);
    }

    public Mono<ServerResponse> findAllDelay(ServerRequest request) {
        Flux<Restaurant> result = restaurantRepository.findAll().delayElements(Duration.ofSeconds(1));
        return ok().contentType(APPLICATION_JSON_UTF8).body(result, Restaurant.class);
    }

    public Mono<ServerResponse> get(ServerRequest request) {
        String id = request.pathVariable("id");
        Mono<Restaurant> result = restaurantRepository.findById(id);
        return ok().contentType(APPLICATION_JSON_UTF8).body(result, Restaurant.class);
    }

    public Mono<ServerResponse> create(ServerRequest request) {
        Flux<Restaurant> restaurants = request.bodyToFlux(Restaurant.class);
        Flux<Restaurant> result = restaurants
                .buffer(10000)
                .flatMap(rs -> reactiveMongoTemplate.insert(rs, Restaurant.class));
        return ok().contentType(APPLICATION_JSON_UTF8).body(result, Restaurant.class);
    }

    public Mono<ServerResponse> delete(ServerRequest request) {
        String id = request.pathVariable("id");
        Mono<Void> result = restaurantRepository.deleteById(id);
        return ok().contentType(APPLICATION_JSON_UTF8).build(result);
    }
}

对比上篇RestaurantController,由于去除了路由信息,RestaurantHandler变得非常函数化,可以说就是一组相关的HandlerFunction的集合,同时各个方法的可复用性也大为提升。这就回答了上一小节提出的疑问,即以牺牲可维护性为代价,换取更好的函数特性。

2.3 单元测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class RestaurantHandlerTests extends BaseUnitTests {

    @Autowired
    private RouterFunction<ServerResponse> restaurantRouter;

    @Override
    protected WebTestClient prepareClient() {
        WebTestClient webClient = WebTestClient.bindToRouterFunction(restaurantRouter)
                .configureClient().baseUrl("http://localhost:9090").responseTimeout(Duration.ofMinutes(1)).build();
        return webClient;
    }
}

和针对Controller的单元测试相比,编写Handler的单元测试的主要区别在于初始化WebTestClient方式的不同,测试方法的主体可以完全复用。

3 小结

到此,有关响应式编程的介绍就暂且告一段落。回顾这四篇文章,我先是从响应式宣言说起,然后介绍了响应式编程的基本概念和关键特性,并且详解了Spring 5中和响应式编程相关的新特性,最后以一个示例应用结尾。希望读完这些文章,对你理解响应式编程能有所帮助。欢迎你到我的留言板分享,和大家一起过过招。

4 参考

【Spring 5】响应式Web框架前瞻

引子:被誉为“中国大数据第一人”的涂子沛先生在其成名作《数据之巅》里提到,摩尔定律、社交媒体、数据挖掘是大数据的三大成因。IBM的研究称,整个人类文明所获得的全部数据中,有90%是过去两年内产生的。在...
  • eMac
  • eMac
  • 2017年05月30日 23:43
  • 6783

spring5.0 函数式web框架 webflux

spring5.0 函数式web框架 webflux2个demo的源码在GitHub上 链接点这里spring50 函数式web框架 webflux 函数式web框架 webFlux什么是函数式 什么...
  • qq_34438958
  • qq_34438958
  • 2017年11月15日 11:47
  • 1345

Spring 5 的 WebFlux 开发反应式 Web 应用

Spring 5 是流行的 Spring 框架的下一个重大的版本升级。Spring 5 中最重要改动是把反应式编程的思想应用到了框架的各个方面,Spring 5 的反应式编程以 Reactor 库为基...
  • moonpure
  • moonpure
  • 2017年10月31日 10:15
  • 1432

【Spring 5】响应式Web框架实战(上)

引子:被誉为“中国大数据第一人”的涂子沛先生在其成名作《数据之巅》里提到,摩尔定律、社交媒体、数据挖掘是大数据的三大成因。IBM的研究称,整个人类文明所获得的全部数据中,有90%是过去两年内产生的。在...
  • eMac
  • eMac
  • 2017年06月30日 00:31
  • 1180

Spring Framework 5.0的响应式微服务

Spring Framework 5.0的响应式微服务作者:chszs,未经博主允许不得转载。经许可的转载需注明作者和博客主页:/chszsSpring团队已经...
  • chszs
  • chszs
  • 2017年02月21日 17:05
  • 4378

Spring 5 新功能:函数式 Web 框架

就像在昨天Juergen发布的博客的一样,Spring 5.0框架第二个里程碑版本中介绍了一个新的函数式web框架。在这篇文章中,我将更详细的介绍这个框架。 紧记该函数式web框架是在Spr...
  • ejinxian
  • ejinxian
  • 2016年10月08日 11:07
  • 6621

spring完整demo实例

  • 2014年11月01日 20:16
  • 19.31MB
  • 下载

在Spring Framework 5.0中引入Kotlin支持

 我们几个月前有介绍关于 Kotlin在start.spring.io上的支持 ,我们有一直在进行工作,以确保Spring和 Kotlin 在一起玩得很好。 Kotlin的一个关键优势是它提供了...
  • hj7jay
  • hj7jay
  • 2017年01月11日 09:45
  • 4261

eclipse搭建(Struts2.5+Spring5.0+hibernate5.2)整合框架Demo实例

  • 2017年12月03日 09:44
  • 21.94MB
  • 下载

netty5.0官方自带的demo

  • 2015年04月12日 17:09
  • 732KB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:【Spring 5】响应式Web框架实战(下)
举报原因:
原因补充:

(最多只允许输入30个字)