├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── org │ │ └── spring5 │ │ ├── Spring5ReactiveApplication.java │ │ ├── client │ │ └── UserClient.java │ │ ├── config │ │ └── HttpServerConfig.java │ │ ├── controller │ │ └── UserController.java │ │ ├── handler │ │ └── UserHandler.java │ │ ├── model │ │ └── User.java │ │ ├── repository │ │ └── UserRepository.java │ │ └── routes │ │ └── Routes.java └── resources │ └── application.properties └── test └── java └── org └── spring5 ├── Spring5ReactiveApplicationTests.java └── UserTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/srpraneeth/spring5-reactive/cbbe5eade61029c59c2094898f08789cf2a6ff0f/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spring5-reactive 2 | 3 | Spring 5 - Spring webflux has a new functional reactive web framework which is non blocking. We can build asynchronous, non blocking, event driven services, that can scale very well. 4 | 5 | Migrating from blocking (imperative) style of coding to functional non blocking reactive style of coding helps to define the business logic as asynchronous function calls. This can be done using Java8 method references or lambda expressions. Since the threads are non blocked, processing power is used to the maximum. 6 | Spring 5 is still in milestone release(5.0.0 M5) as of writing this. 7 | 8 | This is a Sample application for examples of Server and Client to build reactive services. 9 | 10 | Clone the repository and run the main class. 11 | 12 | 13 | To build and Start the application. 14 | 15 | cd spring5-reactive 16 | 17 | mvn spring-boot:run 18 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # 58 | # Look for the Apple JDKs first to preserve the existing behaviour, and then look 59 | # for the new JDKs provided by Oracle. 60 | # 61 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then 62 | # 63 | # Apple JDKs 64 | # 65 | export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home 66 | fi 67 | 68 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then 69 | # 70 | # Apple JDKs 71 | # 72 | export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 73 | fi 74 | 75 | if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then 76 | # 77 | # Oracle JDKs 78 | # 79 | export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 80 | fi 81 | 82 | if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then 83 | # 84 | # Apple JDKs 85 | # 86 | export JAVA_HOME=`/usr/libexec/java_home` 87 | fi 88 | ;; 89 | esac 90 | 91 | if [ -z "$JAVA_HOME" ] ; then 92 | if [ -r /etc/gentoo-release ] ; then 93 | JAVA_HOME=`java-config --jre-home` 94 | fi 95 | fi 96 | 97 | if [ -z "$M2_HOME" ] ; then 98 | ## resolve links - $0 may be a link to maven's home 99 | PRG="$0" 100 | 101 | # need this for relative symlinks 102 | while [ -h "$PRG" ] ; do 103 | ls=`ls -ld "$PRG"` 104 | link=`expr "$ls" : '.*-> \(.*\)$'` 105 | if expr "$link" : '/.*' > /dev/null; then 106 | PRG="$link" 107 | else 108 | PRG="`dirname "$PRG"`/$link" 109 | fi 110 | done 111 | 112 | saveddir=`pwd` 113 | 114 | M2_HOME=`dirname "$PRG"`/.. 115 | 116 | # make it fully qualified 117 | M2_HOME=`cd "$M2_HOME" && pwd` 118 | 119 | cd "$saveddir" 120 | # echo Using m2 at $M2_HOME 121 | fi 122 | 123 | # For Cygwin, ensure paths are in UNIX format before anything is touched 124 | if $cygwin ; then 125 | [ -n "$M2_HOME" ] && 126 | M2_HOME=`cygpath --unix "$M2_HOME"` 127 | [ -n "$JAVA_HOME" ] && 128 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 129 | [ -n "$CLASSPATH" ] && 130 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 131 | fi 132 | 133 | # For Migwn, ensure paths are in UNIX format before anything is touched 134 | if $mingw ; then 135 | [ -n "$M2_HOME" ] && 136 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 137 | [ -n "$JAVA_HOME" ] && 138 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 139 | # TODO classpath? 140 | fi 141 | 142 | if [ -z "$JAVA_HOME" ]; then 143 | javaExecutable="`which javac`" 144 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 145 | # readlink(1) is not available as standard on Solaris 10. 146 | readLink=`which readlink` 147 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 148 | if $darwin ; then 149 | javaHome="`dirname \"$javaExecutable\"`" 150 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 151 | else 152 | javaExecutable="`readlink -f \"$javaExecutable\"`" 153 | fi 154 | javaHome="`dirname \"$javaExecutable\"`" 155 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 156 | JAVA_HOME="$javaHome" 157 | export JAVA_HOME 158 | fi 159 | fi 160 | fi 161 | 162 | if [ -z "$JAVACMD" ] ; then 163 | if [ -n "$JAVA_HOME" ] ; then 164 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 165 | # IBM's JDK on AIX uses strange locations for the executables 166 | JAVACMD="$JAVA_HOME/jre/sh/java" 167 | else 168 | JAVACMD="$JAVA_HOME/bin/java" 169 | fi 170 | else 171 | JAVACMD="`which java`" 172 | fi 173 | fi 174 | 175 | if [ ! -x "$JAVACMD" ] ; then 176 | echo "Error: JAVA_HOME is not defined correctly." >&2 177 | echo " We cannot execute $JAVACMD" >&2 178 | exit 1 179 | fi 180 | 181 | if [ -z "$JAVA_HOME" ] ; then 182 | echo "Warning: JAVA_HOME environment variable is not set." 183 | fi 184 | 185 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 186 | 187 | # For Cygwin, switch paths to Windows format before running java 188 | if $cygwin; then 189 | [ -n "$M2_HOME" ] && 190 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 191 | [ -n "$JAVA_HOME" ] && 192 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 193 | [ -n "$CLASSPATH" ] && 194 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 195 | fi 196 | 197 | # traverses directory structure from process work directory to filesystem root 198 | # first directory with .mvn subdirectory is considered project base directory 199 | find_maven_basedir() { 200 | local basedir=$(pwd) 201 | local wdir=$(pwd) 202 | while [ "$wdir" != '/' ] ; do 203 | if [ -d "$wdir"/.mvn ] ; then 204 | basedir=$wdir 205 | break 206 | fi 207 | wdir=$(cd "$wdir/.."; pwd) 208 | done 209 | echo "${basedir}" 210 | } 211 | 212 | # concatenates all lines of a file 213 | concat_lines() { 214 | if [ -f "$1" ]; then 215 | echo "$(tr -s '\n' ' ' < "$1")" 216 | fi 217 | } 218 | 219 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} 220 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 221 | 222 | # Provide a "standardized" way to retrieve the CLI args that will 223 | # work with both Windows and non-Windows executions. 224 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 225 | export MAVEN_CMD_LINE_ARGS 226 | 227 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 228 | 229 | exec "$JAVACMD" \ 230 | $MAVEN_OPTS \ 231 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 232 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 233 | ${WRAPPER_LAUNCHER} "$@" 234 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | set MAVEN_CMD_LINE_ARGS=%* 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | 121 | set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" 122 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 123 | 124 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% 125 | if ERRORLEVEL 1 goto error 126 | goto end 127 | 128 | :error 129 | set ERROR_CODE=1 130 | 131 | :end 132 | @endlocal & set ERROR_CODE=%ERROR_CODE% 133 | 134 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 135 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 136 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 137 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 138 | :skipRcPost 139 | 140 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 141 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 142 | 143 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 144 | 145 | exit /B %ERROR_CODE% -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.spring5 7 | spring5-reactive 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | spring5-reactive 12 | 13 | 14 | org.springframework.boot 15 | spring-boot-starter-parent 16 | 2.0.0.BUILD-SNAPSHOT 17 | 18 | 19 | 20 | 21 | UTF-8 22 | UTF-8 23 | 1.8 24 | 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-webflux 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-test 38 | test 39 | 40 | 41 | 42 | 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-maven-plugin 47 | 48 | 49 | 50 | 51 | 52 | 53 | spring-snapshots 54 | Spring Snapshots 55 | https://repo.spring.io/snapshot 56 | 57 | true 58 | 59 | 60 | 61 | spring-milestones 62 | Spring Milestones 63 | https://repo.spring.io/milestone 64 | 65 | false 66 | 67 | 68 | 69 | 70 | 71 | 72 | spring-snapshots 73 | Spring Snapshots 74 | https://repo.spring.io/snapshot 75 | 76 | true 77 | 78 | 79 | 80 | spring-milestones 81 | Spring Milestones 82 | https://repo.spring.io/milestone 83 | 84 | false 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/main/java/org/spring5/Spring5ReactiveApplication.java: -------------------------------------------------------------------------------- 1 | package org.spring5; 2 | 3 | import java.io.IOException; 4 | 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | 8 | @SpringBootApplication 9 | public class Spring5ReactiveApplication { 10 | 11 | public static void main(String[] args) throws IOException { 12 | SpringApplication.run(Spring5ReactiveApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/spring5/client/UserClient.java: -------------------------------------------------------------------------------- 1 | package org.spring5.client; 2 | 3 | import java.util.List; 4 | 5 | import org.spring5.model.User; 6 | import org.springframework.http.MediaType; 7 | import org.springframework.web.reactive.function.client.ClientResponse; 8 | import org.springframework.web.reactive.function.client.WebClient; 9 | 10 | public class UserClient { 11 | 12 | public static void main(String[] args) { 13 | 14 | // 15 | ClientResponse response = WebClient.create("http://localhost:9000").get().uri("/api/user") 16 | .accept(MediaType.APPLICATION_JSON).exchange().block(); 17 | assert response.statusCode().value() == 200; 18 | List users = response.bodyToFlux(User.class).collectList().block(); 19 | assert users.size() == 2; 20 | assert users.iterator().next().getUser().equals("User1"); 21 | 22 | // 23 | User user = WebClient.create("http://localhost:9000").get().uri("/api/user/1") 24 | .accept(MediaType.APPLICATION_JSON).exchange().flatMap(resp -> resp.bodyToMono(User.class)).block(); 25 | assert user.getId() == 1; 26 | assert user.getUser().equals("User1"); 27 | 28 | response = WebClient.create("http://localhost:9000").get().uri("/api/user/10") 29 | .accept(MediaType.APPLICATION_JSON).exchange().block(); 30 | assert response.statusCode().value() == 404; 31 | 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/spring5/config/HttpServerConfig.java: -------------------------------------------------------------------------------- 1 | package org.spring5.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.core.env.Environment; 7 | import org.springframework.http.server.reactive.HttpHandler; 8 | import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter; 9 | import org.springframework.web.reactive.function.server.RouterFunction; 10 | import org.springframework.web.reactive.function.server.RouterFunctions; 11 | 12 | import reactor.ipc.netty.http.server.HttpServer; 13 | 14 | @Configuration 15 | public class HttpServerConfig { 16 | 17 | @Autowired 18 | private Environment environment; 19 | 20 | @Bean 21 | public HttpServer httpServer(RouterFunction routerFunction) { 22 | HttpHandler httpHandler = RouterFunctions.toHttpHandler(routerFunction); 23 | ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler); 24 | HttpServer server = HttpServer.create("localhost", Integer.valueOf(environment.getProperty("server.port"))); 25 | server.newHandler(adapter); 26 | return server; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/spring5/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package org.spring5.controller; 2 | 3 | import org.spring5.model.User; 4 | import org.springframework.http.MediaType; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.PathVariable; 7 | import org.springframework.web.reactive.function.client.WebClient; 8 | import org.springframework.web.reactive.function.server.ServerResponse; 9 | 10 | import reactor.core.publisher.Mono; 11 | 12 | //@RestController("/api/v2") 13 | public class UserController { 14 | 15 | @GetMapping("/user") 16 | public Mono handleGetUsers() { 17 | return WebClient.create("http://localhost:9000").get().uri("/api/user") 18 | .accept(MediaType.APPLICATION_JSON).exchange().flatMap(resp -> ServerResponse.ok().body(resp.bodyToFlux(User.class), User.class)); 19 | } 20 | 21 | @GetMapping("/user/{id}") 22 | public Mono handleGetUserById(@PathVariable String id) { 23 | return WebClient.create("http://localhost:9000").get().uri("/api/user/" + id) 24 | .accept(MediaType.APPLICATION_JSON).exchange().flatMap(resp -> ServerResponse.ok().body(resp.bodyToMono(User.class), User.class)); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/spring5/handler/UserHandler.java: -------------------------------------------------------------------------------- 1 | package org.spring5.handler; 2 | 3 | import org.spring5.model.User; 4 | import org.spring5.repository.UserRepository; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.web.reactive.function.server.ServerRequest; 8 | import org.springframework.web.reactive.function.server.ServerResponse; 9 | 10 | import reactor.core.publisher.Mono; 11 | 12 | @Service 13 | public class UserHandler { 14 | 15 | @Autowired 16 | private UserRepository userRepository; 17 | 18 | public Mono handleGetUsers(ServerRequest request) { 19 | return ServerResponse.ok().body(userRepository.getUsers(), User.class); 20 | } 21 | 22 | public Mono handleGetUserById(ServerRequest request) { 23 | return userRepository.getUserById(request.pathVariable("id")) 24 | .flatMap(user -> ServerResponse.ok().body(Mono.just(user), User.class)) 25 | .switchIfEmpty(ServerResponse.notFound().build()); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/spring5/model/User.java: -------------------------------------------------------------------------------- 1 | package org.spring5.model; 2 | 3 | public class User { 4 | 5 | public User(){} 6 | 7 | public User(Long id, String user) { 8 | this.id = id; 9 | this.user = user; 10 | } 11 | 12 | private Long id; 13 | private String user; 14 | 15 | public Long getId() { 16 | return id; 17 | } 18 | 19 | public void setId(Long id) { 20 | this.id = id; 21 | } 22 | 23 | public String getUser() { 24 | return user; 25 | } 26 | 27 | public void setUser(String user) { 28 | this.user = user; 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return "User [id=" + id + ", user=" + user + "]"; 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/main/java/org/spring5/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package org.spring5.repository; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import org.spring5.model.User; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import reactor.core.publisher.Flux; 10 | import reactor.core.publisher.Mono; 11 | 12 | @Repository 13 | public class UserRepository { 14 | 15 | private final List users = Arrays.asList(new User(1L, "User1"), new User(2L, "User2")); 16 | 17 | public Mono getUserById(String id) { 18 | return Mono.justOrEmpty(users.stream().filter(user -> { 19 | return user.getId().equals(Long.valueOf(id)); 20 | }).findFirst().orElse(null)); 21 | } 22 | 23 | public Flux getUsers() { 24 | return Flux.fromIterable(users); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/spring5/routes/Routes.java: -------------------------------------------------------------------------------- 1 | package org.spring5.routes; 2 | 3 | import static org.springframework.web.reactive.function.server.RequestPredicates.GET; 4 | import static org.springframework.web.reactive.function.server.RouterFunctions.route; 5 | import static org.springframework.web.reactive.function.server.RequestPredicates.accept; 6 | 7 | import org.spring5.handler.UserHandler; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.http.MediaType; 11 | import org.springframework.web.reactive.function.server.RouterFunction; 12 | 13 | @Configuration 14 | public class Routes { 15 | 16 | private UserHandler userHandler; 17 | 18 | public Routes(UserHandler userHandler) { 19 | this.userHandler = userHandler; 20 | } 21 | 22 | @Bean 23 | public RouterFunction routerFunction() { 24 | return 25 | route(GET("/api/user").and(accept(MediaType.APPLICATION_JSON)), userHandler::handleGetUsers) 26 | .and(route(GET("/api/user/{id}").and(accept(MediaType.APPLICATION_JSON)), userHandler::handleGetUserById)); 27 | // .and(route(POST("/users"), userHandler::handleGetUsers)); 28 | 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=9000 -------------------------------------------------------------------------------- /src/test/java/org/spring5/Spring5ReactiveApplicationTests.java: -------------------------------------------------------------------------------- 1 | package org.spring5; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class Spring5ReactiveApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/org/spring5/UserTest.java: -------------------------------------------------------------------------------- 1 | package org.spring5; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.spring5.model.User; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; 12 | import org.springframework.http.MediaType; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | import org.springframework.test.web.reactive.server.FluxExchangeResult; 15 | import org.springframework.test.web.reactive.server.WebTestClient; 16 | 17 | @RunWith(SpringRunner.class) 18 | @SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT) 19 | public class UserTest { 20 | 21 | @Autowired 22 | private WebTestClient webTestClient; 23 | 24 | @Test 25 | public void test() throws IOException { 26 | FluxExchangeResult result = webTestClient.get().uri("/api/user").accept(MediaType.APPLICATION_JSON) 27 | .exchange().returnResult(User.class); 28 | assert result.getStatus().value() == 200; 29 | List users = result.getResponseBody().collectList().block(); 30 | assert users.size() == 2; 31 | assert users.iterator().next().getUser().equals("User1"); 32 | } 33 | 34 | @Test 35 | public void test1() throws IOException { 36 | User user = webTestClient.get().uri("/api/user/1") 37 | .accept(MediaType.APPLICATION_JSON).exchange().returnResult(User.class).getResponseBody().blockFirst(); 38 | assert user.getId() == 1; 39 | assert user.getUser().equals("User1"); 40 | } 41 | 42 | @Test 43 | public void test2() throws IOException { 44 | webTestClient.get().uri("/api/user/10").accept(MediaType.APPLICATION_JSON).exchange().expectStatus() 45 | .isNotFound(); 46 | } 47 | 48 | } 49 | --------------------------------------------------------------------------------