spring的webflux初探

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

* pom.xml

<?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>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 ReactiveRedisConnection 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

猜你喜欢

转载自blog.csdn.net/tjq980303/article/details/79222350