diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 562a109..dd24251 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [ '8' ] + java: [ '17', '23' ] architecture: [ 'x64' ] name: Build with JDK ${{ matrix.java }} on ${{ matrix.architecture }} steps: @@ -30,5 +30,5 @@ jobs: restore-keys: ${{ runner.os }}-maven- - name: Run Tests - run: mvn verify -Ptests + run: mvn verify diff --git a/.run/Template JUnit.run.xml b/.run/Template JUnit.run.xml new file mode 100644 index 0000000..2fdad57 --- /dev/null +++ b/.run/Template JUnit.run.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/docs/modules/ROOT/pages/hazelcast-embedded-springboot.adoc b/docs/modules/ROOT/pages/hazelcast-embedded-springboot.adoc index 31d8771..49b7085 100644 --- a/docs/modules/ROOT/pages/hazelcast-embedded-springboot.adoc +++ b/docs/modules/ROOT/pages/hazelcast-embedded-springboot.adoc @@ -30,58 +30,75 @@ If Hazelcast is on the classpath and a suitable configuration is found, Spring B include::ROOT:example$hazelcast-embedded-springboot/pom.xml[tag=hazelcast-dep] ---- -Hazelcast configuration (`hazelcast.yaml`) is placed in the `src/main/resources/` directory. You only need to auto-wire the `HazelcastInstance` bean in the `CommandController` and use it to access to Hazelcast data structures: +Hazelcast configuration (`hazelcast.yaml`) is placed in the `src/main/resources/` directory. You only need to define `map` bean, if you want to autowire the IMap instance: +[source,java,indent=0] +---- +include::ROOT:example$hazelcast-embedded-springboot/src/main/java/guides/hazelcast/springboot/HazelcastApplication.java[tag=imap-bean] +---- + +Bean will have name "map" and can be autowired by `IMap` type (if it's the only IMap added as bean). + +Now you can autowire the `IMap` bean in the `CommandController` and use it to access the Hazelcast structure: [source,java,indent=0] ---- include::ROOT:example$hazelcast-embedded-springboot/src/main/java/guides/hazelcast/springboot/CommandController.java[] ---- +Please notice, that we've used `@Qualifier("map")` - it's strictly speaking optional, but once you add more IMaps, you will need to distinguish which map you want to autowire. + == Run the Sample Application Run the application using Maven in a terminal: +[source,bash] ---- -mvn spring-boot:run -Dspring-boot.run.jvmArguments="-Dserver.port=8080" +mvn spring-boot:run -Dspring-boot.run.jvmArguments="-Dserver.port=8080 -Djava.net.preferIPv4Stack=true" ---- Then, rerun the application in another terminal. NOTE: Notice the different value for the `server.port` argument. +NOTE: We've used `-Djava.net.preferIPv4Stack=true`, because on some platforms there are problems with multicast on IPv6. Adding this option ensures smooth run when learning. + +[source,bash] ---- -mvn spring-boot:run -Dspring-boot.run.jvmArguments="-Dserver.port=8081" +mvn spring-boot:run -Dspring-boot.run.jvmArguments="-Dserver.port=8081 -Djava.net.preferIPv4Stack=true" ---- After both application instances are initialized, you should see that the Hazelcast cluster is formed: -```bash +[source,bash] +---- Members {size:2, ver:2} [ Member [192.168.1.64]:5701 - 520aec3f-58a6-4fcb-a3c7-498dcf37d8ff Member [192.168.1.64]:5702 - 5c03e467-d457-4847-b49a-745a335db557 this ] -``` +---- Now, you can issue HTTP requests to put and get data back. Run the following command to put the data into a Hazelcast distributed map: -```bash +[source,bash] +---- curl --data "key=key1&value=hazelcast" "localhost:8080/put" -``` +---- You will see the value in the output. Then run the command below to get the data back. Please note that the call is made to the other application instance: -```bash +[source,bash] +---- curl "localhost:8081/get?key=key1" -``` +---- -Again, you will see the value in the output since the data is distributed among Hazelcast cluster instances and can be accessed from any of them. +Again, you will see the value in the output (`{"value":"hazelcast"}`), because the data is distributed among Hazelcast cluster instances and can be accessed from any of them. == Test the Application To run the integration tests, run the following command in terminal. But before, make sure to kill the running application instances. ---- -mvn verify -Ptests +mvn verify ---- If the tests pass, you’ll see a similar output to the following: diff --git a/pom.xml b/pom.xml index bd64f34..9203662 100644 --- a/pom.xml +++ b/pom.xml @@ -11,13 +11,12 @@ org.springframework.boot spring-boot-starter-parent - 2.4.1 - + 3.4.1 - 1.8 - 5.3.2 + 17 + 5.5.0 @@ -26,35 +25,34 @@ org.springframework.boot spring-boot-maven-plugin + + maven-failsafe-plugin + 3.5.2 + + + + integration-test + verify + + + + + -Djava.net.preferIPv4Stack=true + + - - - tests - - - - maven-failsafe-plugin - 2.22.2 - - - - integration-test - verify - - - - - - - - - org.springframework.boot spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + @@ -74,11 +72,24 @@ spring-boot-starter-webflux test + + org.springframework.boot + spring-boot-starter-log4j2 + + + org.apache.logging.log4j + log4j-spring-boot + net.minidev json-smart - 2.3 + 2.5.1 test + + org.awaitility + awaitility + 4.2.2 + diff --git a/src/main/java/guides/hazelcast/springboot/CommandController.java b/src/main/java/guides/hazelcast/springboot/CommandController.java index 13f61d1..bb62a34 100644 --- a/src/main/java/guides/hazelcast/springboot/CommandController.java +++ b/src/main/java/guides/hazelcast/springboot/CommandController.java @@ -1,32 +1,27 @@ package guides.hazelcast.springboot; -import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.map.IMap; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.util.concurrent.ConcurrentMap; - @RestController public class CommandController { - @Autowired - private HazelcastInstance hazelcastInstance; - - private ConcurrentMap retrieveMap() { - return hazelcastInstance.getMap("map"); - } + @Autowired @Qualifier("map") + private IMap map; @PostMapping("/put") public CommandResponse put(@RequestParam(value = "key") String key, @RequestParam(value = "value") String value) { - retrieveMap().put(key, value); + map.put(key, value); return new CommandResponse(value); } @GetMapping("/get") public CommandResponse get(@RequestParam(value = "key") String key) { - String value = retrieveMap().get(key); + String value = map.get(key); return new CommandResponse(value); } } diff --git a/src/main/java/guides/hazelcast/springboot/CommandResponse.java b/src/main/java/guides/hazelcast/springboot/CommandResponse.java index a14999a..39174ad 100644 --- a/src/main/java/guides/hazelcast/springboot/CommandResponse.java +++ b/src/main/java/guides/hazelcast/springboot/CommandResponse.java @@ -1,18 +1,4 @@ package guides.hazelcast.springboot; -public class CommandResponse { - - private String value; - - public CommandResponse(String value) { - this.value = value; - } - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } +public record CommandResponse (String value) { } diff --git a/src/main/java/guides/hazelcast/springboot/HazelcastApplication.java b/src/main/java/guides/hazelcast/springboot/HazelcastApplication.java index 4880335..286e604 100644 --- a/src/main/java/guides/hazelcast/springboot/HazelcastApplication.java +++ b/src/main/java/guides/hazelcast/springboot/HazelcastApplication.java @@ -1,7 +1,10 @@ package guides.hazelcast.springboot; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.map.IMap; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @@ -10,4 +13,11 @@ public class HazelcastApplication { public static void main(String[] args) { SpringApplication.run(HazelcastApplication.class, args); } + + //tag::imap-bean[] + @Bean + public IMap map(HazelcastInstance instance) { + return instance.getMap("map"); + } + //end::imap-bean[] } diff --git a/src/main/resources/hazelcast.yaml b/src/main/resources/hazelcast.yaml index 627d281..bb15dcd 100644 --- a/src/main/resources/hazelcast.yaml +++ b/src/main/resources/hazelcast.yaml @@ -1,2 +1,8 @@ hazelcast: - cluster-name: hazelcast-cluster \ No newline at end of file + cluster-name: hazelcast-cluster + properties: + hazelcast.logging.type: log4j2 + network: + join: + multicast: + enabled: true diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml new file mode 100644 index 0000000..7e0d17a --- /dev/null +++ b/src/main/resources/log4j2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/test/java/guides/hazelcast/springboot/CommandControllerIT.java b/src/test/java/guides/hazelcast/springboot/CommandControllerIT.java index 9f5430c..8d5ca91 100644 --- a/src/test/java/guides/hazelcast/springboot/CommandControllerIT.java +++ b/src/test/java/guides/hazelcast/springboot/CommandControllerIT.java @@ -1,5 +1,6 @@ package guides.hazelcast.springboot; +import com.hazelcast.config.Config; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; import org.junit.jupiter.api.Test; @@ -9,7 +10,9 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.reactive.server.WebTestClient; -import static org.junit.jupiter.api.Assertions.assertEquals; +import java.time.Duration; + +import static org.awaitility.Awaitility.await; import static org.springframework.http.HttpHeaders.ACCEPT; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @@ -26,7 +29,11 @@ public class CommandControllerIT { @Test public void testPutRequest(){ //when - WebTestClient.ResponseSpec responseSpec = makePostRequest("/put?key={key}&value={value}", "key1", "value1"); + WebTestClient.ResponseSpec responseSpec = webTestClient + .post() + .uri("/put?key={key}&value={value}", "key1", "value1") + .header(ACCEPT, APPLICATION_JSON_VALUE) + .exchange(); //then responseSpec.expectStatus() @@ -40,7 +47,11 @@ public void testPutRequest(){ @Test public void testGetRequest(){ //given - makePostRequest("/put?key={key}&value={value}", "key1", "value1"); + webTestClient + .post() + .uri("/put?key={key}&value={value}", "key1", "value1") + .header(ACCEPT, APPLICATION_JSON_VALUE) + .exchange(); //when WebTestClient.ResponseSpec responseSpec = webTestClient @@ -58,20 +69,21 @@ public void testGetRequest(){ .jsonPath("$.value").isEqualTo("value1"); } - private WebTestClient.ResponseSpec makePostRequest(String uri, Object... parameters) { - return webTestClient - .post() - .uri(uri, parameters) - .header(ACCEPT, APPLICATION_JSON_VALUE) - .exchange(); - } - @Test - public void testHazelcastCluster(){ + public void testHazelcastCluster() { + Config config = Config.load(); + config.setProperty("hazelcast.logging.type", "log4j2"); + //given - Hazelcast.newHazelcastInstance(); + HazelcastInstance hz = Hazelcast.newHazelcastInstance(); //then - assertEquals(2, hazelcastInstance.getCluster().getMembers().size()); + try { + await() + .atMost(Duration.ofMinutes(2)) + .until(() -> hazelcastInstance.getCluster().getMembers().size() == 2); + } finally { + hz.shutdown(); + } } } diff --git a/src/test/resources/hazelcast.yaml b/src/test/resources/hazelcast.yaml index 34ee506..bb15dcd 100644 --- a/src/test/resources/hazelcast.yaml +++ b/src/test/resources/hazelcast.yaml @@ -1,5 +1,7 @@ hazelcast: cluster-name: hazelcast-cluster + properties: + hazelcast.logging.type: log4j2 network: join: multicast: diff --git a/src/test/resources/log4j2.xml b/src/test/resources/log4j2.xml new file mode 100644 index 0000000..7e0d17a --- /dev/null +++ b/src/test/resources/log4j2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + +