spring的webflux初探
2018-01-31 21:34
197 查看
spring的webflux初探
不久前, spring进行了较大的改动, 主要目的是为了增加对响应式编程的支持.spring 默认是采用了reactor项目作为响应式编程(reactive programming)的支持, 我也以此作为基础来谈.
reactor项目地址: https://github.com/reactor/reactor
为什么要reactor
总的来说, reactor也是一个用于编写异步代码的库, 众所周知, 对于同步程序来说, 有IO耗时长之类的开销. 所以人们不断的推崇使用异步的方式来编写一些代码, 而java也提供了编写异步程序的方法给开发者, 那么我们为什么需要reactor. 就我短时间的使用体验来说, reactor使我们编写异步代码变得更加简单快捷, 让某项工作更加简单或让其更有效率, 我觉得就是一个库应该解决的问题, 显然reactor做到了, 在使用了reactor后, 你就再也不用写callback那种又臭又长的面条代码了, 代码的可读性与可维护性大大加强了. 相比于future, reactor又提供了更多功能齐全的操作, 编程复杂的也大大降低好了, 我们并不是来介绍reactor的, 更多有关reactor的资料以及它与jvm其他异步方式的对比请参考reactor文档: http://projectreactor.io/docs/core/release/reference
webflux实例
webflux与webmvc的类比webmvc | webflux |
---|---|
controller | handler |
request mapping | router |
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cn.edu.ncu</groupId> <artifactId>reactive-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>reactive-demo</name> <description>Demo project for Spring Boot</description> <parent> <groupId&g 4000 t;org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.0.M7</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-webflux --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.mindrot/jbcrypt --> <dependency> <groupId>org.mindrot</groupId> <artifactId>jbcrypt</artifactId> <version>0.4</version> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.44</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </pluginRepository> <pluginRepository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> </pluginRepositories> </project>
编写handler
先写个hello world handler练练手
package cn.edu.ncu.reactivedemo.handlers; @Service public class HelloWorldHandler { public Mono<ServerResponse> helloWorld(ServerRequest request){ return ServerResponse.ok() .contentType(MediaType.TEXT_PLAIN) .body(BodyInserters.fromObject("hello world")); } }
注册路由
将写好的handler注册到路由上
package cn.edu.ncu.reactivedemo; @Configuration public class Router { @Autowired private HelloWorldHandler helloWorldHandler; @Autowired private UserHandler userHandler; @Bean public RouterFunction<?> routerFunction(){ return RouterFunctions.route(RequestPredicates.GET("/hello"), helloWorldHandler::helloWorld); } }
启动类
默认采用netty作为reactor的底层启动
package cn.edu.ncu.reactivedemo; @SpringBootApplication public class ReactiveDemoApplication { public static void main(String[] args) { SpringApplication.run(ReactiveDemoApplication.class, args); } }
访问http://127.0.0.1:8080/hello
返回hello world表示成功
使用数据库
暂时支持reactive编程的数据库只有MongoDB, redis, Cassandra, Couchbase我们直接采用redis作为测试, 做一个简陋的注册登录的接口就行了
* 配置redis
package cn.edu.ncu.reactivedemo.config; @Configuration public class RedisConfig { @Autowired private RedisConnectionFactory factory; @Bean public ReactiveRedisTemplate<String, String> reactiveRedisTemplate(ReactiveRedisConnectionFactory connectionFactory){ return new ReactiveRedisTemplate<String, String>(connectionFactory, RedisSerializationContext.string()); } @Bean public Rea d9b7 ctiveRedisConnection connection(ReactiveRedisConnectionFactory connectionFactory){ return connectionFactory.getReactiveConnection(); } public @PreDestroy void flushDb(){ factory.getConnection().flushDb(); } }
测试redis接口
package cn.edu.ncu.reactivedemo; @RunWith(SpringRunner.class) @SpringBootTest(classes = ReactiveDemoApplication.class) public class RedisTests { @Autowired private ReactiveRedisConnection connection; @Test public void testRedis(){ connection .stringCommands().set(ByteBuffer.wrap("h".getBytes()), ByteBuffer.wrap("w".getBytes())) .subscribe(System.out::println); } }
编写userHandler
package cn.edu.ncu.reactivedemo.handlers; @Service public class UserHandler { @Autowired private ReactiveRedisConnection connection; public Mono<ServerResponse> register(ServerRequest request) { Mono<Map> body = request.bodyToMono(Map.class); return body.flatMap(map -> { String username = (String) map.get("username"); String password = (String) map.get("password"); String hashedPassword = BCrypt.hashpw(password, BCrypt.gensalt()); return connection.stringCommands() .set(ByteBuffer.wrap(username.getBytes()), ByteBuffer.wrap(hashedPassword.getBytes())); }).flatMap(aBoolean -> { Map<String, String> result = new HashMap<>(); ServerResponse serverResponse = null; if (aBoolean){ result.put("message", "successful"); return ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON_UTF8) .body(BodyInserters.fromObject(result)); }else { result.put("message", "failed"); return ServerResponse.status(HttpStatus.BAD_REQUEST) .contentType(MediaType.APPLICATION_JSON_UTF8) .body(BodyInserters.fromObject(request)); } }); } public Mono<ServerResponse> login(ServerRequest request){ Mono<Map> body = request.bodyToMono(Map.class); return body.flatMap(map -> { String username = (String) map.get("username"); String password = (String) map.get("password"); return connection.stringCommands().get(ByteBuffer.wrap(username.getBytes())).flatMap(byteBuffer -> { byte[] bytes = new byte[byteBuffer.remaining()]; byteBuffer.get(bytes, 0, bytes.length); String hashedPassword = null; try { hashedPassword = new String(bytes, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } Map<String, String> result = new HashMap<>(); if (hashedPassword == null || !BCrypt.checkpw(password, hashedPassword)){ result.put("message", "账号或密码错误"); return ServerResponse.status(HttpStatus.UNAUTHORIZED) .contentType(MediaType.APPLICATION_JSON_UTF8) .body(BodyInserters.fromObject(result)); }else { result.put("token", "假token"); return ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON_UTF8) .body(BodyInserters.fromObject(result)); } }); }); } }
添加router
package cn.edu.ncu.reactivedemo; @Configuration public class Router { @Autowired private HelloWorldHandler helloWorldHandler; @Autowired private UserHandler userHandler; @Bean public RouterFunction<?> routerFunction(){ return RouterFunctions.route(RequestPredicates.GET("/hello"), helloWorldHandler::helloWorld) .andRoute(RequestPredicates.POST("/register"), userHandler::register) .andRoute(RequestPredicates.POST("/login"), userHandler::login); } }
接口很粗糙,没有写model层, 也没有数据验证, 测试也直接用http requester进行测试了
参考:
https://spring.io/blog/2016/11/28/going-reactive-with-spring-data
http://projectreactor.io/docs/core/release/reference/
http://projectreactor.io/docs/core/release/api/
https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-fn-handler-functions
demo地址:
https://github.com/ncuwaln/spring-reactive-demo
相关文章推荐
- Spring学习之Spring MVC 初探
- Ignite初探-ignite集成spring boot 类转换异常classCastException
- Velocity在spring中的配置和使用初探
- Spring Boot初探之数据库访问
- Spring之框架初探
- 【初探Spring】------Spring IOC(一)
- Webcollector + Spring + MVC 搭建应用初探(六)(Lenskit 推荐系统实例)
- 【初探Spring】——Spring IOC(一)
- 【初探Spring】——Spring IOC(三):初始化过程—Resource定位
- Spring Cloud Eureka 初探
- 初探spring之IoC实现
- 项目架构之spring初探
- Spring 初探(十三)(Spring Boot annotation)
- Spring.net----初探IOC容器
- SSH三大框架初探之Spring
- 【初探Spring】——Spring IOC(二):初始化过程—简介
- Spring Boot 初探之JSP
- Velocity初探小结--Velocity在spring中的配置和使用
- Sping Boot 初探(一)--- 一分钟构建SpringWeb
- Spring Boot - 初探