diff --git a/build.gradle b/build.gradle index 6879be0..7f603fd 100644 --- a/build.gradle +++ b/build.gradle @@ -4,34 +4,50 @@ plugins { id 'io.spring.dependency-management' version '1.0.15.RELEASE' } -group = 'com.example' -version = '0.0.1-SNAPSHOT' - -java { - sourceCompatibility = '11' +repositories { + mavenCentral() } -configurations { - compileOnly { - extendsFrom annotationProcessor +subprojects { + apply plugin: 'java' + apply plugin: 'org.springframework.boot' + apply plugin: 'io.spring.dependency-management' + + group = 'com.example' + version = '0.0.1-SNAPSHOT' + + java { + sourceCompatibility = '11' } -} -repositories { - mavenCentral() -} + configurations { + compileOnly { + extendsFrom annotationProcessor + } + } -dependencies { - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-webflux' - compileOnly 'org.projectlombok:lombok' - developmentOnly 'org.springframework.boot:spring-boot-devtools' - annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' - annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'io.projectreactor:reactor-test' -} + configurations { + compileOnly { + extendsFrom annotationProcessor + } + } -tasks.named('test') { - useJUnitPlatform() -} + repositories { + mavenCentral() + } + + dependencies { + implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-webflux' + compileOnly 'org.projectlombok:lombok' + developmentOnly 'org.springframework.boot:spring-boot-devtools' + annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + } + + tasks.named('test') { + useJUnitPlatform() + } +} \ No newline at end of file diff --git a/helloreactor/build.gradle b/helloreactor/build.gradle new file mode 100644 index 0000000..5de588b --- /dev/null +++ b/helloreactor/build.gradle @@ -0,0 +1,18 @@ +plugins { + id 'java' +} + +version 'unspecified' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/helloreactor/src/main/java/com/example/HelloreactorApplication.java b/helloreactor/src/main/java/com/example/HelloreactorApplication.java new file mode 100644 index 0000000..0117a2b --- /dev/null +++ b/helloreactor/src/main/java/com/example/HelloreactorApplication.java @@ -0,0 +1,17 @@ +package com.example; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import reactor.core.publisher.Flux; + +@SpringBootApplication +public class HelloreactorApplication { + + public static void main(String[] args) { + SpringApplication.run(HelloreactorApplication.class, args); + + Flux sequence = Flux.just("Hello", "Reactor"); // 데이터 생성 및 제공 + sequence.map(String::toLowerCase) // 데이터 가공 + .subscribe(System.out::println); // 전달받은 데이터 처리 + } +} diff --git a/marblediagram/build.gradle b/marblediagram/build.gradle new file mode 100644 index 0000000..91f0175 --- /dev/null +++ b/marblediagram/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java' +} + +version 'unspecified' + +repositories { + mavenCentral() +} + +dependencies { + implementation 'com.jayway.jsonpath:json-path:2.7.0' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/marblediagram/src/main/java/com/example/MarblediagramApplication.java b/marblediagram/src/main/java/com/example/MarblediagramApplication.java new file mode 100644 index 0000000..1b038e6 --- /dev/null +++ b/marblediagram/src/main/java/com/example/MarblediagramApplication.java @@ -0,0 +1,12 @@ +package com.example; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class MarblediagramApplication { + + public static void main(String[] args) { + SpringApplication.run(MarblediagramApplication.class, args); + } +} diff --git a/marblediagram/src/main/java/com/example/mono/Example1.java b/marblediagram/src/main/java/com/example/mono/Example1.java new file mode 100644 index 0000000..786284c --- /dev/null +++ b/marblediagram/src/main/java/com/example/mono/Example1.java @@ -0,0 +1,14 @@ +package com.example.mono; + +import reactor.core.publisher.Mono; + +public class Example1 { + + /** + * Reactor 의 Publisher 타입 중 하나인 Mono 를 사용하여 문자열 출력 + */ + public static void main(String[] args) { + Mono.just("Hello Reactor") // 하나의 문자열! + .subscribe(System.out::println); + } +} diff --git a/marblediagram/src/main/java/com/example/mono/Example2.java b/marblediagram/src/main/java/com/example/mono/Example2.java new file mode 100644 index 0000000..eaa2a16 --- /dev/null +++ b/marblediagram/src/main/java/com/example/mono/Example2.java @@ -0,0 +1,17 @@ +package com.example.mono; + +import reactor.core.publisher.Mono; + +public class Example2 { + + public static void main(String[] args) { + Mono + .empty() // 데이터 한 건도 emit 하지 않음 + .subscribe( + none -> System.out.println("# emitted onNext Signal"), + error -> {}, + () -> System.out.println("# emitted onComplete Signal") + // 아무 데이터를 전송하지 emit 하지 않고 onComplete Signal 만을 전송 + ); + } +} diff --git a/marblediagram/src/main/java/com/example/mono/Example3.java b/marblediagram/src/main/java/com/example/mono/Example3.java new file mode 100644 index 0000000..317a99a --- /dev/null +++ b/marblediagram/src/main/java/com/example/mono/Example3.java @@ -0,0 +1,47 @@ +package com.example.mono; + +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.JsonPath; +import java.net.URI; +import java.util.Collections; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; +import reactor.core.publisher.Mono; + +public class Example3 { + + public static void main(String[] args) { + URI worldTimeUri = UriComponentsBuilder.newInstance().scheme("http") + .host("worldtimeapi.org") + .port(80) + .path("/api/timezone/Asia/Seoul") + .build() + .encode() + .toUri(); + + RestTemplate restTemplate = new RestTemplate(); + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + + Mono.just( + restTemplate + .exchange(worldTimeUri, HttpMethod.GET, new HttpEntity(headers), String.class) + ) + .map(response -> { + DocumentContext jsonContext = JsonPath.parse(response.getBody()); + String dateTime = jsonContext.read("$.datetime"); + return dateTime; + }) + .subscribe( + data -> System.out.println("# emitted data: " + data), + error -> { + System.out.println(error); + }, + () -> System.out.println("# emitted onComplete signal") + ); + } +} diff --git a/marblediagram/src/main/java/com/example/mono/Example4.java b/marblediagram/src/main/java/com/example/mono/Example4.java new file mode 100644 index 0000000..08280f2 --- /dev/null +++ b/marblediagram/src/main/java/com/example/mono/Example4.java @@ -0,0 +1,12 @@ +package com.example.mono; + +import reactor.core.publisher.Flux; + +public class Example4 { + + public static void main(String[] args) { + Flux.just(6, 9, 13) + .map(num -> num % 2) + .subscribe(System.out::println); + } +} diff --git a/marblediagram/src/main/java/com/example/mono/Example5.java b/marblediagram/src/main/java/com/example/mono/Example5.java new file mode 100644 index 0000000..f014175 --- /dev/null +++ b/marblediagram/src/main/java/com/example/mono/Example5.java @@ -0,0 +1,13 @@ +package com.example.mono; + +import reactor.core.publisher.Flux; + +public class Example5 { + + public static void main(String[] args) { + Flux.fromArray(new Integer[]{3, 6, 7, 9}) + .filter(num -> num > 6) + .map(num -> num * 2) + .subscribe(System.out::println); + } +} diff --git a/marblediagram/src/main/java/com/example/mono/Example6.java b/marblediagram/src/main/java/com/example/mono/Example6.java new file mode 100644 index 0000000..ce24a81 --- /dev/null +++ b/marblediagram/src/main/java/com/example/mono/Example6.java @@ -0,0 +1,14 @@ +package com.example.mono; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public class Example6 { + + public static void main(String[] args) { + Flux flux = Mono.justOrEmpty("Steve") + .concatWith(Mono.justOrEmpty("Jobs")); + + flux.subscribe(System.out::println); + } +} diff --git a/marblediagram/src/main/java/com/example/mono/Example7.java b/marblediagram/src/main/java/com/example/mono/Example7.java new file mode 100644 index 0000000..468449b --- /dev/null +++ b/marblediagram/src/main/java/com/example/mono/Example7.java @@ -0,0 +1,16 @@ +package com.example.mono; + +import reactor.core.publisher.Flux; + +public class Example7 { + + public static void main(String[] args) { + Flux.concat( + Flux.just("Mercury", "Venus", "Earth"), + Flux.just("Mars", "Jupiter", "Saturn"), + Flux.just("Uranus", "Neptune", "Pluto") + ) + .collectList() + .subscribe(planets -> System.out.println(planets)); + } +} diff --git a/settings.gradle b/settings.gradle index 1b2bda0..fb0e931 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,9 @@ rootProject.name = 'springwebflux' +include 'springMvcHeadOffice' +include 'springMvcHeadOffice' +include 'springMvcBranchOffice' +include 'springReactiveHeadOffice' +include 'springReactiveBranchOffice' +include 'helloreactor' +include 'marblediagram' + diff --git a/springMvcBranchOffice/build.gradle b/springMvcBranchOffice/build.gradle new file mode 100644 index 0000000..5de588b --- /dev/null +++ b/springMvcBranchOffice/build.gradle @@ -0,0 +1,18 @@ +plugins { + id 'java' +} + +version 'unspecified' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/springMvcBranchOffice/src/main/java/com/example/springMvcBranchOffice/SpringMvcBranchOfficeApplication.java b/springMvcBranchOffice/src/main/java/com/example/springMvcBranchOffice/SpringMvcBranchOfficeApplication.java new file mode 100644 index 0000000..e401977 --- /dev/null +++ b/springMvcBranchOffice/src/main/java/com/example/springMvcBranchOffice/SpringMvcBranchOfficeApplication.java @@ -0,0 +1,29 @@ +package com.example.springMvcBranchOffice; + +import com.example.springMvcBranchOffice.domain.Book; +import java.util.HashMap; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@Slf4j +@SpringBootApplication +public class SpringMvcBranchOfficeApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringMvcBranchOfficeApplication.class, args); + } + + @Bean + public Map bookMap() { + Map bookMap = new HashMap<>(); + + for (long i = 1; i <= 5; ++i) { + bookMap.put(i, new Book(i, "book" + i)); + } + + return bookMap; + } +} diff --git a/springMvcBranchOffice/src/main/java/com/example/springMvcBranchOffice/controller/SpringMvcBranchOfficeController.java b/springMvcBranchOffice/src/main/java/com/example/springMvcBranchOffice/controller/SpringMvcBranchOfficeController.java new file mode 100644 index 0000000..c467c08 --- /dev/null +++ b/springMvcBranchOffice/src/main/java/com/example/springMvcBranchOffice/controller/SpringMvcBranchOfficeController.java @@ -0,0 +1,38 @@ +package com.example.springMvcBranchOffice.controller; + +import com.example.springMvcBranchOffice.domain.Book; +import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/v1/books") +public class SpringMvcBranchOfficeController { + + private Map bookMap; + + @Autowired + public SpringMvcBranchOfficeController(Map bookMap) { + this.bookMap = bookMap; + } + + /** + * HeadOffice 에서 bookId 를 통해 책을 조회하는 요청이 왔음 + */ + @ResponseStatus(HttpStatus.OK) + @GetMapping("/{book-id}") + public ResponseEntity getBook(@PathVariable("book-id") long bookId) throws InterruptedException { + // HeadOffice 요청을 처리하는 스레드가 차단되는지 BranchOffice 의 요청을 처리하는 스레드 5초를 sleep 하여 시간 확인 + Thread.sleep(5000); + + Book book = bookMap.get(bookId); + + return ResponseEntity.ok(book); + } +} diff --git a/springMvcBranchOffice/src/main/java/com/example/springMvcBranchOffice/domain/Book.java b/springMvcBranchOffice/src/main/java/com/example/springMvcBranchOffice/domain/Book.java new file mode 100644 index 0000000..06165e7 --- /dev/null +++ b/springMvcBranchOffice/src/main/java/com/example/springMvcBranchOffice/domain/Book.java @@ -0,0 +1,12 @@ +package com.example.springMvcBranchOffice.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class Book { + + private long id; + private String name; +} diff --git a/springMvcBranchOffice/src/main/resources/application.yml b/springMvcBranchOffice/src/main/resources/application.yml new file mode 100644 index 0000000..88137b8 --- /dev/null +++ b/springMvcBranchOffice/src/main/resources/application.yml @@ -0,0 +1,2 @@ +server: + port: 7070 \ No newline at end of file diff --git a/springMvcHeadOffice/build.gradle b/springMvcHeadOffice/build.gradle new file mode 100644 index 0000000..5de588b --- /dev/null +++ b/springMvcHeadOffice/build.gradle @@ -0,0 +1,18 @@ +plugins { + id 'java' +} + +version 'unspecified' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/springMvcHeadOffice/src/main/java/com/example/springMvcHeadOffice/SpringMvcHeadOfficeApplication.java b/springMvcHeadOffice/src/main/java/com/example/springMvcHeadOffice/SpringMvcHeadOfficeApplication.java new file mode 100644 index 0000000..4200dd4 --- /dev/null +++ b/springMvcHeadOffice/src/main/java/com/example/springMvcHeadOffice/SpringMvcHeadOfficeApplication.java @@ -0,0 +1,64 @@ +package com.example.springMvcHeadOffice; + +import com.example.springMvcHeadOffice.dto.Book; +import java.net.URI; +import java.time.LocalDateTime; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +@Slf4j +@SpringBootApplication +public class SpringMvcHeadOfficeApplication { + + private URI baseUri = UriComponentsBuilder.newInstance().scheme("http") + .host("localhost") + .port(8080) + .path("/v1/books") + .build() + .encode() + .toUri(); + + public static void main(String[] args) { + SpringApplication.run(SpringMvcHeadOfficeApplication.class, args); + } + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + + @Bean + public CommandLineRunner run() { + return (String... args) -> { + log.info("# 요청 시작 시간 : {}", LocalDateTime.now()); + + for (int i = 1; i <= 5; ++i) { + Book book = this.getBook(i); + log.info("{} : book name : {}", LocalDateTime.now(), book.getName()); // 5번 책 조회하니 약 25초 결과나옴 -> 스레드 차단확인! + } + }; + } + + private Book getBook(long bookId) { + RestTemplate restTemplate = new RestTemplate(); + + URI getBookUri = UriComponentsBuilder.fromUri(baseUri) + .path("/{book-id}") + .build() + .expand(bookId) + .encode() + .toUri(); // http://localhost:8080/v1/books/{book-id} + + ResponseEntity response = restTemplate.getForEntity(getBookUri, Book.class); + Book book = response.getBody(); + + return book; + } +} diff --git a/springMvcHeadOffice/src/main/java/com/example/springMvcHeadOffice/controller/SpringMvcHeadOfficeController.java b/springMvcHeadOffice/src/main/java/com/example/springMvcHeadOffice/controller/SpringMvcHeadOfficeController.java new file mode 100644 index 0000000..7d896a0 --- /dev/null +++ b/springMvcHeadOffice/src/main/java/com/example/springMvcHeadOffice/controller/SpringMvcHeadOfficeController.java @@ -0,0 +1,51 @@ +package com.example.springMvcHeadOffice.controller; + +import com.example.springMvcHeadOffice.dto.Book; +import java.net.URI; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +@Slf4j +@RequiredArgsConstructor +@RestController +@RequestMapping("/v1/books") +public class SpringMvcHeadOfficeController { + + private final RestTemplate restTemplate; + + URI baseUri = UriComponentsBuilder.newInstance().scheme("http") + .host("localhost") + .port(7070) + .path("v1/books") + .build() + .encode() + .toUri(); + + /** + * HeadOffice 에서 특정 bookId를 통해 책을 찾아달라는 요청을 받으면 RestTemplate 을 통하여 BranchOffice에 책을 찾아달라는 요청을 함 (Blocking I/O) + */ + @ResponseStatus(HttpStatus.OK) + @GetMapping("/{book-id}") + public ResponseEntity getBook(@PathVariable("book-id") long bookId) { + URI getBookUri = UriComponentsBuilder.fromUri(baseUri) + .path("/{book-id}") + .build() + .expand(bookId) + .encode() + .toUri(); // http://localhost:7070/v1/books/{book-id} + + ResponseEntity response = restTemplate.getForEntity(getBookUri, Book.class); + Book book = response.getBody(); + + return ResponseEntity.ok(book); + } +} diff --git a/springMvcHeadOffice/src/main/java/com/example/springMvcHeadOffice/dto/Book.java b/springMvcHeadOffice/src/main/java/com/example/springMvcHeadOffice/dto/Book.java new file mode 100644 index 0000000..1d51c2c --- /dev/null +++ b/springMvcHeadOffice/src/main/java/com/example/springMvcHeadOffice/dto/Book.java @@ -0,0 +1,10 @@ +package com.example.springMvcHeadOffice.dto; + +import lombok.Getter; + +@Getter +public class Book { + + private long id; + private String name; +} \ No newline at end of file diff --git a/springMvcHeadOffice/src/main/resources/application.yml b/springMvcHeadOffice/src/main/resources/application.yml new file mode 100644 index 0000000..47fbb02 --- /dev/null +++ b/springMvcHeadOffice/src/main/resources/application.yml @@ -0,0 +1,2 @@ +server: + port: 8080 \ No newline at end of file diff --git a/springReactiveBranchOffice/build.gradle b/springReactiveBranchOffice/build.gradle new file mode 100644 index 0000000..5de588b --- /dev/null +++ b/springReactiveBranchOffice/build.gradle @@ -0,0 +1,18 @@ +plugins { + id 'java' +} + +version 'unspecified' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/springReactiveBranchOffice/src/main/java/com/example/springReactiveBranchOffice/SpringReactiveBranchOffice.java b/springReactiveBranchOffice/src/main/java/com/example/springReactiveBranchOffice/SpringReactiveBranchOffice.java new file mode 100644 index 0000000..148d899 --- /dev/null +++ b/springReactiveBranchOffice/src/main/java/com/example/springReactiveBranchOffice/SpringReactiveBranchOffice.java @@ -0,0 +1,27 @@ +package com.example.springReactiveBranchOffice; + +import com.example.springReactiveBranchOffice.domain.Book; +import java.util.HashMap; +import java.util.Map; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication +public class SpringReactiveBranchOffice { + + public static void main(String[] args) { + SpringApplication.run(SpringReactiveBranchOffice.class, args); + } + + @Bean + public Map bookMap() { + Map bookMap = new HashMap<>(); + + for (long i = 1; i <= 5; ++i) { + bookMap.put(i, new Book(i, "book" + i)); + } + + return bookMap; + } +} diff --git a/springReactiveBranchOffice/src/main/java/com/example/springReactiveBranchOffice/controller/SpringReactiveBranchOfficeController.java b/springReactiveBranchOffice/src/main/java/com/example/springReactiveBranchOffice/controller/SpringReactiveBranchOfficeController.java new file mode 100644 index 0000000..8c389ed --- /dev/null +++ b/springReactiveBranchOffice/src/main/java/com/example/springReactiveBranchOffice/controller/SpringReactiveBranchOfficeController.java @@ -0,0 +1,33 @@ +package com.example.springReactiveBranchOffice.controller; + +import com.example.springReactiveBranchOffice.domain.Book; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +@Slf4j +@RequestMapping("/v1/books") +@RestController +@AllArgsConstructor +public class SpringReactiveBranchOfficeController { + + private Map bookMap; + + @ResponseStatus(HttpStatus.OK) + @GetMapping("/{book-id}") + public Mono getBook(@PathVariable("book-id") long bookId) throws InterruptedException { + // HeadOffice 요청을 처리하는 스레드가 차단되는지 BranchOffice 의 요청을 처리하는 스레드 5초를 sleep 하여 시간 확인 + Thread.sleep(5000); + + Book book = bookMap.get(bookId); + + return Mono.just(book); + } +} diff --git a/springReactiveBranchOffice/src/main/java/com/example/springReactiveBranchOffice/domain/Book.java b/springReactiveBranchOffice/src/main/java/com/example/springReactiveBranchOffice/domain/Book.java new file mode 100644 index 0000000..5483710 --- /dev/null +++ b/springReactiveBranchOffice/src/main/java/com/example/springReactiveBranchOffice/domain/Book.java @@ -0,0 +1,12 @@ +package com.example.springReactiveBranchOffice.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class Book { + + private long id; + private String name; +} diff --git a/springReactiveBranchOffice/src/main/resources/application.yml b/springReactiveBranchOffice/src/main/resources/application.yml new file mode 100644 index 0000000..13d1899 --- /dev/null +++ b/springReactiveBranchOffice/src/main/resources/application.yml @@ -0,0 +1,2 @@ +server: + port: 5050 \ No newline at end of file diff --git a/springReactiveHeadOffice/build.gradle b/springReactiveHeadOffice/build.gradle new file mode 100644 index 0000000..5de588b --- /dev/null +++ b/springReactiveHeadOffice/build.gradle @@ -0,0 +1,18 @@ +plugins { + id 'java' +} + +version 'unspecified' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/springReactiveHeadOffice/src/main/java/com/example/springReactiveHeadOffice/SpringReactiveHeadOfficeApplication.java b/springReactiveHeadOffice/src/main/java/com/example/springReactiveHeadOffice/SpringReactiveHeadOfficeApplication.java new file mode 100644 index 0000000..4bba22b --- /dev/null +++ b/springReactiveHeadOffice/src/main/java/com/example/springReactiveHeadOffice/SpringReactiveHeadOfficeApplication.java @@ -0,0 +1,64 @@ +package com.example.springReactiveHeadOffice; + +import com.example.springReactiveHeadOffice.dto.Book; +import java.net.URI; +import java.time.LocalDateTime; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.util.UriComponentsBuilder; +import reactor.core.publisher.Mono; + +@Slf4j +@SpringBootApplication +public class SpringReactiveHeadOfficeApplication { + + private URI baseUri = UriComponentsBuilder.newInstance().scheme("http") + .host("localhost") + .port(6060) + .path("/v1/books") + .build() + .encode() + .toUri(); + + public static void main(String[] args) { + System.setProperty("reactor.netty.ioWorkerCount", "1"); + SpringApplication.run(SpringReactiveHeadOfficeApplication.class, args); + } + + @Bean + public CommandLineRunner run() { + return (String... args) -> { + log.info("# 요청 시작 시간 : {}", LocalDateTime.now()); + + for (int i = 1; i <= 5; ++i) { + // Book book = this.getBook(i) 을 쓰지 않고 subscribe() 에서 응답 데이터를 전달받은 후에 처리 + // HeadOffice API 로부터 전달받은 응답이 Mono 타입이고 Mono 는 Reactor 에서 지원하는 Publisher 타입 중 하나이기 때문 + // Publisher 인터페이스 경우 subscribe() 를 호출하여 전달받은 데이터 처리하도록 정의 + // Mono 또한 Reactor 에서 지원하는 Publisher 타입 + this.getBook(i) + .subscribe( + book -> log.info("{} : book name: {}", LocalDateTime.now(), book.getName()) + ); + } + }; + } + + private Mono getBook(long bookId) { + URI getBookUri = UriComponentsBuilder.fromUri(baseUri) + .path("/{book-id}") + .build() + .expand(bookId) + .encode() + .toUri(); // http://localhost:6060/v1/books/{book-id} + + return WebClient.create() + .get() + .uri(getBookUri) + .retrieve() + .bodyToMono(Book.class); + } +} diff --git a/springReactiveHeadOffice/src/main/java/com/example/springReactiveHeadOffice/controller/SpringReactiveHeadOfficeController.java b/springReactiveHeadOffice/src/main/java/com/example/springReactiveHeadOffice/controller/SpringReactiveHeadOfficeController.java new file mode 100644 index 0000000..af29cc5 --- /dev/null +++ b/springReactiveHeadOffice/src/main/java/com/example/springReactiveHeadOffice/controller/SpringReactiveHeadOfficeController.java @@ -0,0 +1,49 @@ +package com.example.springReactiveHeadOffice.controller; + +import com.example.springReactiveHeadOffice.dto.Book; +import java.net.URI; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.util.UriComponentsBuilder; +import reactor.core.publisher.Mono; + +@Slf4j +@RequestMapping("/v1/books") +@RestController +public class SpringReactiveHeadOfficeController { + + private URI baseUri = UriComponentsBuilder.newInstance().scheme("http") + .host("localhost") + .port(5050) + .path("/v1/books") + .build() + .encode() + .toUri(); + + @Autowired + public SpringReactiveHeadOfficeController() {} + + @ResponseStatus(HttpStatus.OK) + @GetMapping("/{book-id}") + public Mono getBook(@PathVariable("book-id") long bookId) { + URI getBookUri = UriComponentsBuilder.fromUri(baseUri) + .path("/{book-id}") + .build() + .expand(bookId) + .encode() + .toUri(); // http://localhost:5050/v1/books/{book-id} + + return WebClient.create() + .get() + .uri(getBookUri) + .retrieve() + .bodyToMono(Book.class); + } +} diff --git a/springReactiveHeadOffice/src/main/java/com/example/springReactiveHeadOffice/dto/Book.java b/springReactiveHeadOffice/src/main/java/com/example/springReactiveHeadOffice/dto/Book.java new file mode 100644 index 0000000..2668cb2 --- /dev/null +++ b/springReactiveHeadOffice/src/main/java/com/example/springReactiveHeadOffice/dto/Book.java @@ -0,0 +1,10 @@ +package com.example.springReactiveHeadOffice.dto; + +import lombok.Getter; + +@Getter +public class Book { + + private long id; + private String name; +} \ No newline at end of file diff --git a/springReactiveHeadOffice/src/main/resources/application.yml b/springReactiveHeadOffice/src/main/resources/application.yml new file mode 100644 index 0000000..e92bb98 --- /dev/null +++ b/springReactiveHeadOffice/src/main/resources/application.yml @@ -0,0 +1,2 @@ +server: + port: 6060 \ No newline at end of file diff --git a/src/main/java/com/example/springwebflux/SpringwebfluxApplication.java b/src/main/java/com/example/springwebflux/SpringwebfluxApplication.java deleted file mode 100644 index eb0b81e..0000000 --- a/src/main/java/com/example/springwebflux/SpringwebfluxApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example.springwebflux; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class SpringwebfluxApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringwebfluxApplication.class, args); - } - -}