【Spring 5】Spring Webfluxを使用したReactiveアプリケーションの開発

9393 ワード

Spring 5-Spring webfluxは、非同期、非ブロック、イベント駆動のサービスを構築するために使用できる新しい非ブロッキング関数式Reactive Webフレームワークであり、拡張性に優れています.
ブロック(避けられない)スタイルのコードを関数式の非ブロックReactiveスタイルコードに移行するには、ビジネスロジックを非同期関数として呼び出す必要がある.これはJava 8の方法やlambda式を参照することができる.スレッドが非ブロックであるため、処理能力を最大化して使用することができる.
この記事をリリースしたとき、Spring 5はマイルストーンバージョン(5.0.0 M 5)にありました.
Spring Boostプロジェクトを作成するには、Spring initializerでSpring Bootプロジェクトを作成します.以下の依存をpomに追加する.xmlで

    
        org.springframework.boot
        spring-boot-starter
    
    
        org.springframework.boot
        spring-boot-starter-webflux
    
    
        org.springframework.boot
        spring-boot-starter-test
        test
    

Spring-boot-starter-webfluxパッケージにはspring-webflux,nettyが付いています.他の依存は自分で追加する必要があります.
簡単なユーザデータテーブルとlistからuserデータを取得するDTOクラスを確立します.これは仮想データbeanにすぎないが、Rdbms、MongoDb、RestClientのような他のデータソースからリアルタイムでデータをロードすることができる.JDBCは生まれながらにして応答式ではないため、データベースへの呼び出しはこのスレッドをブロックします.MongoDBには応答型のクライアントドライバがあります.レスポンスWebサービスをテストするときのさらなるレンダリングでは、RESTスタイルの呼び出しはブロックされません.
public class User {
public User(){}

public User(Long id, String user) {
this.id = id;
this.user = user;
}

private Long id;
private String user;

public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUser() { return user; }
public void setUser(String user) { this.user = user; }
}

@Repository
public class UserRepository {
private final List users = Arrays.asList(new User(1L, "User1"), new User(2L, "User2"));

public Mono getUserById(String id) {
return Mono.justOrEmpty(users.stream().filter(user -> {
return user.getId().equals(Long.valueOf(id));
}).findFirst().orElse(null));
}

public Flux getUsers() {
return Flux.fromIterable(users);
}
}

MonoおよびFluxは、ターゲット反応器によって提供される応答タイプである.Springsはまた、RXJavaなどの他の応答ストリームの実装を提供する.
MonoとFluxはReactive streamsのパブリッシャが実現しています.Monoは0または任意の単一値のパブリケーションであり、Fluxは0から任意の値のパブリケーションである.彼らはRXJavaのFlowableやObservableと似ています.彼らはこれらの購読者に情報を公開する代わりに.
GetUserById()は、いつでも0~1個のユーザオブジェクトを返し、GetUsers()は一連の変動したユーザオブジェクトを返し、いつでも0~n個のユーザオブジェクトを含むMonoオブジェクトを返します.
コマンドプログラミングスタイルと比較して、使用可能な前ブロックスレッドのUser/Listオブジェクトを返すのではなく、ストリームの参照を返すだけで、ストリームは後でUser/Listにアクセスできます.
HTTPリクエストを処理する関数を持つHandlerクラスの作成
@Service
public class UserHandler {
@Autowired
private UserRepository userRepository;

public Mono handleGetUsers(ServerRequest request) {
return ServerResponse.ok().body(userRepository.getUsers(), User.class);
}

public Mono handleGetUserById(ServerRequest request) {
return userRepository.getUserById(request.pathVariable("id"))
.flatMap(user -> ServerResponse.ok().body(Mono.just(user), User.class))
.switchIfEmpty(ServerResponse.notFound().build());
}
}

handlerクラスはSpring Webのサービスbeansのように、このサービスのほとんどのビジネス機能を記述する必要があります.ServerResponseはSpring WebのResponseEntityクラスのように、ServerResponseオブジェクトでResponseのデータ、ステータスコード、ヘッダ情報などをパッケージングできます.ServerResponseには、たとえばnotFound()
ok()
accepted()
created()
など、さまざまなタイプのフィードバックを作成できます.
UserHandlerには異なる方法があり、Monoを返します.UserRepository.getUsers()はFluxを返します.およびServerResponse.ok().body(UserRepository.getUsers(), User.class)
このFluxはMonoに変換できます.これは、使用可能な場合にのみ、ServerResponseのストリームを開始できることを示します.UserRepository.getUserById()
Monoを返しますServerResponse.ok().body(Mono.just(user), User.class)
このMonoをMonoに変換すると、サーバResponseのストリームがいつでも開始できることを示します.
サーバResponse.notFound()は、指定するパス変数(pathVariable)にユーザが見つからない場合に使用する.build()は、404サービス応答を返すストリームであるMonoを返す.
コマンドプログラミングスタイルでは、データ受信前のスレッドがブロックされ続け、データが来る前にスレッドが実行できなくなります.応答プログラミングでは、データを取得するストリームを定義し、データが到着した後のコールバック関数操作を定義します.これにより、スレッドが詰まることなく、データが返されると実行に使用できるスレッドが使用されます.
アプリケーションのルーティングを定義するルーティングクラスを作成する
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
@Configuration
public class Routes {
private UserHandler userHandler;

    public Routes(UserHandler userHandler) {
this.userHandler = userHandler;
}

@Bean
public RouterFunction> routerFunction() {
return route(GET("/api/user").and(accept(MediaType.APPLICATION_JSON)), userHandler::handleGetUsers)
.and(route(GET("/api/user/{id}").and(accept(MediaType.APPLICATION_JSON)), userHandler::handleGetUserById));
}
}

RouterFunctionはSpring Webの@RequestMappingクラスのようです.RouterFunctionはSpring 5アプリケーションのルーティングを定義します.RouterFunctionsヘルプクラスには、ルーティングを定義し、RouterFunctionオブジェクトを構築するための有用な方法があります.RequestPredicatesには、GET、POST、path、queryParam、accept、headers、contentTypeなど、ルーティングを定義し、RouterFunctionを構築するための多くの有用な方法があります.各ルーティングは、適切なHttpRequestが受信された場合に呼び出されるハンドラメソッドにマッピングされる.
Spring 5では、アプリケーションハンドラマッピングを定義する@RequestMappingタイプのコントローラもサポートされています.@RequestMappingスタイルで類似のAPIを作成するには、以下に示すコントローラメソッドを記述します.
@GetMapping("/user") public Mono handleGetUsers() {}

コントローラメソッドはMonoを返します.RouterFunctionは、アプリケーションにDSLタイプのルーティング機能を提供します.これまでSpringsでは混合はサポートされていませんでした.
HttpServerConfigクラスの作成HttpServerConfigクラスの作成
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import reactor.ipc.netty.http.server.HttpServer;
@Configuration
public class HttpServerConfig {
@Autowired
private Environment environment;

@Bean
public HttpServer httpServer(RouterFunction> routerFunction) {
HttpHandler httpHandler = RouterFunctions.toHttpHandler(routerFunction);
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
HttpServer server = HttpServer.create("localhost", Integer.valueOf(environment.getProperty("server.port")));
server.newHandler(adapter);
return server;
}
}

これにより、アプリケーション・プロパティで定義されたポートを使用してnetty HttpServerが作成されます.Springがサポートする他のサーバもTomcatやundertowと同じです.Nettyは非同期であり、生まれつきイベント駆動に基づいているため、応答型のアプリケーションに適しています.TomcatはJava NIOを使用してservlet仕様を実装します.NettyはNIOの実装であり、非同期、イベント駆動の非ブロックIOアプリケーションを最適化しています.
Tomcatサーバは、次のようなコードで使用することもできます.
Tomcat tomcatServer = new Tomcat();
    tomcatServer.setHostname("localhost");
    tomcatServer.setPort(Integer.valueOf(environment.getProperty("server.port")));
    Context rootContext = tomcatServer.addContext("", System.getProperty("java.io.tmpdir"));
    ServletHttpHandlerAdapter servlet = new ServletHttpHandlerAdapter(httpHandler);
    Tomcat.addServlet(rootContext, "httpHandlerServlet", servlet);
    rootContext.addServletMapping("/", "httpHandlerServlet");
    tomcatServer.start();

アプリケーションを起動するSpring起動マスタークラスの作成
@SpringBootApplication
public class Spring5ReactiveApplication {
public static void main(String[] args) throws IOException {
SpringApplication.run(Spring5ReactiveApplication.class, args);
}
}

アプリケーションのテスト
Postman、CURLなどのHTTPテストツールを使用して、アプリケーションをテストできます.
Springテストは、応答型サービスの統合テストを作成する機能もサポートします.
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
public class UserTest {
@Autowired
private WebTestClient webTestClient;

@Test
public void test() throws IOException {
FluxExchangeResult result = webTestClient.get().uri("/api/user").accept(MediaType.APPLICATION_JSON)
.exchange().returnResult(User.class);
assert result.getStatus().value() == 200;
List users = result.getResponseBody().collectList().block();
assert users.size() == 2;
assert users.iterator().next().getUser().equals("User1");
}

@Test
public void test1() throws IOException {
User user = webTestClient.get().uri("/api/user/1")
.accept(MediaType.APPLICATION_JSON).exchange().returnResult(User.class).getResponseBody().blockFirst();
assert user.getId() == 1;
assert user.getUser().equals("User1");
}

@Test
public void test2() throws IOException {
webTestClient.get().uri("/api/user/10").accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
.isNotFound();
}
}

WebTestClientはTestRestTemplateと同様にSpring起動アプリケーションを呼び出すrestメソッドを持ち、応答結果を検証することができます.testの構成でSpringテストはTestRestTemplateのbeanを作成した.この中にWebClientがあります.Spring WebのRestTemplateと似ています.これは、応答式および非ブロックrest呼び出しを処理するために使用することができる.
WebClient.create("http://localhost:9000").get().uri("/api/user/1")
        .accept(MediaType.APPLICATION_JSON).exchange().flatMap(resp -> resp.bodyToMono(User.class)).block();

Exchange()は、Emits clientResponseが使用可能なときにストリームを表すMonoを返します.
block()は、MonoがUser/Listを返すまでスレッドをブロックします.これは、応答を検証するためにデータが必要なテスト例です.Spring Webは開発/デバッグが容易であるため必要である.Spring 5応答式またはSpring Webコマンド式サービスを使用する決定は、使用例に従って賢明に行わなければならない.多くの場合、コマンドのみが良いかもしれませんが、高い拡張性が重要な要素である場合、応答式の非ブロックはより適しています.