【Spring 5】响应式Web框架实战(下)
2017-07-18 01:23
423 查看
引子:被誉为“中国大数据第一人”的涂子沛先生在其成名作《数据之巅》里提到,摩尔定律、社交媒体、数据挖掘是大数据的三大成因。IBM的研究称,整个人类文明所获得的全部数据中,有90%是过去两年内产生的。在此背景下,包括NoSQL,Hadoop, Spark, Storm, Kylin在内的大批新技术应运而生。其中以RxJava和Reactor为代表的响应式(Reactive)编程技术针对的就是经典的大数据4V定义(Volume,Variety,Velocity,Value)中的Velocity,即高并发问题,而在即将发布的Spring 5中,也引入了响应式编程的支持。在接下来的几周,我会围绕响应式编程分三期与你分享我的一些学习心得。本篇是第三篇(下),通过一个简单的Spring 5示例应用,探一探即将于下月底发布的Spring 5的究竟。
前情概要:
- 【Spring 5】响应式Web框架前瞻
- 响应式编程总览
- 【Spring 5】响应式Web框架实战(上)
Router Functions是Spring 5新引入的一套Reactive风格(基于Flux和Mono)的函数式接口,主要包括
和Router Functions搭配使用的是两个新的请求/响应模型,
可以看到,使用Router Functions实现RP应用时,你需要自己创建和管理容器,也就是说Spring 5并没有针对Router Functions提供IoC支持,这是Router Functions和Spring MVC相比最大的不同。除此之外,你需要通过
对比上篇的
和针对Controller的单元测试相比,编写Handler的单元测试的主要区别在于初始化
spring-framework Reactive Tests
poutsma/web-function-sample
前情概要:
- 【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)的函数式接口,主要包括
RouterFunction,
HandlerFunction和
HandlerFilterFunction,分别对应Spring MVC中的
@RequestMapping,
@Controller和
HandlerInterceptor(或者Servlet规范中的
Filter)。
和Router Functions搭配使用的是两个新的请求/响应模型,
ServerRequest和
ServerResponse,这两个模型同样提供了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 Framework Reference - WebFlux frameworkspring-framework Reactive Tests
poutsma/web-function-sample
相关文章推荐
- 【Spring 5】响应式Web框架实战(上)
- spring-boot+mybatis开发实战:如何在spring-boot中使用myabtis持久层框架
- 批量更新实战 框架spring mybatis 和MySQL
- spring2的AOP框架-AOP实战
- (实战篇)SSM三大框架整合详细教程(Spring+SpringMVC+MyBatis)
- Struct2、Hibernate3、Spring3框架搭建实战(转)
- spring-boot+mybatis开发实战:如何在spring-boot中使用myabtis持久层框架
- Spring整合定时任务调度框架Quartz实战
- Spring的声明式事务管理(李刚 轻量级javaEE 企业应用实战-三大框架整合开发)
- spring整合quartz框架定时任务实战
- Struct2、Hibernate3、Spring3框架搭建实战
- spring4学习记录03-spring mvc实战之框架构建
- spring4学习记录05-spring mvc实战之框架构建测试jpa借助spring-data实现自动化jpa
- Java 权限框架 Shiro 实战二:与spring集成、filter机制
- 实战 :Spring MVC + 注解 +SqlServer 框架搭建及详解
- spring-boot框架搭建小白实战 出现的问题及需要注意的问题之一
- springMVC+redis+mybatis的javaWeb框架的搭建---套路实战(1)
- 新鲜出炉--Struct2、Hibernate3、Spring3框架搭建实战
- Spring+Hibernate+Struts(SSH)框架整合实战
- 响应式Web框架实战(上)