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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章