Это вторая попытка с пересмотренным демонстрационным кодом, который, надеюсь, лучше иллюстрирует проблему. Код был урезан, чтобы удалить все элементы, кроме тех, которые демонстрируют возникшую проблему.
Добавить примечание: Было проведено дополнительное тестирование, и результаты опубликованы как ответ (недостаток, расширяющий этоПочта). Возможно, это «ожидаемое поведение», но я все еще пытаюсь понять «почему».
Код «работает» в том смысле, что возвращает ожидаемую информацию (либо строка, либо список строк). Однако, когда WebClient используется для доступа к конечной точке REST ( localhost: 8080 / test / DemoClient ), которая возвращает Flux, в соответствующий обработчик выполняется два вызова ( DemoMainHandler.getAll ()). Я не вижу, где выполняется второй вызов DemoMainHandler.getAll () , но я обеспокоен потенциальными проблемами с производительностью, если это произойдет в производственной среде.
В предоставленном кодевсе выполняется в одном приложении Spring Webflux, поэтому для кода DemoClient не существует отдельного процесса.
Доступ к конечной точке REST на localhost: 8080 / test / DemoClient /2 , кажется, работает правильно, возвращая Mono Почтальону со значением «Only One». Что еще более важно, DemoMainHandler.getById () вызывается только один раз.
Однако доступ к конечной точке REST в localhost: 8080 / test / DemoClient дает результаты, которые являютсянемного касательно. Значения String, возвращаемые Postman через Flux, выглядят нормально, но
- DemoClientHandler.getAll () вызывается при доступе к конечной точке REST
- * DemoClientHandler.getAll () вызывает DemoClient.getAll ()
- DemoClient.getAll () использует WebClient для доступа к конечной точке REST на localhost:8080 / test / DemoMain
- DemoClient.getAll () использует flatMapMany для итерации по возвращенному ClientResponse и извлечения потока изтело ответа
- Поток, сгенерированный DemoCLient.getAll () , возвращается в DemoClientHandler.getAll ()
- DemoClientHandler.getAll() проверяет поток, определяет, имеет ли он один или несколько элементов, и возвращает поток в ServerResponse начальному клиенту (в данном случае, Postman)
- Postmanзатем распаковывает Flux (map? flatMap?) и отображает возвращенные строки ("CallMeOnce")
Я не понимаю, почему DemoClientHandler.getAll () вызывается второй раз, как указано вторым System.out.println () вывод на консоль. Похоже, это связано с использованием Flux в качестве типа возвращаемого значения?
Добавить примечание: При вероятности, что проблема была каким-то образом вызвана конструкцией .exchange (). FlatMapMany () , Я пытался использовать .retrieve (). BodyToFlux () конструкция (см. Прокомментированный код в DemoClient ). Тот же результат (т. Е. DemoMainHandler.getAll () , кажется, вызывается дважды).
Консольный вывод
2019-10-07 08:16:18.953 INFO 9384 --- [ main] c.example.testdupe.TestDupeApplication : Starting TestDupeApplication on M7730-LFR with PID 9384 (D:\sandbox\TestDupe\build\classes\java\main started by LesR in D:\sandbox\TestDupe)
2019-10-07 08:16:18.953 INFO 9384 --- [ main] c.example.testdupe.TestDupeApplication : No active profile set, falling back to default profiles: default
2019-10-07 08:16:20.062 INFO 9384 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8080
2019-10-07 08:16:20.062 INFO 9384 --- [ main] c.example.testdupe.TestDupeApplication : Started TestDupeApplication in 1.324 seconds (JVM running for 1.871)
***** Invoke localhost:8080/test/DemoClient/{id}
DemoClientHandler.getById( ServerRequest )
DemoClient.getById( 2 )
DemoMainHandler.getById( ServerRequest )
***** Invoke localhost:8080/test/DemoClient
DemoClientHandler.getAll( ServerRequest )
DemoClientHandler.getAll() >> BEFORE invoking demoClient.getAll()
DemoClient.getAll()
DemoClient.getAll() >> RETURN fluxString
DemoClientHandler.getAll() >> AFTER invoking demoClient.getAll()
DemoMainHandler.getAll( ServerRequest )
DemoMainHandler.getAll( ServerRequest )
Пример кода
@SpringBootApplication
public class TestDupeApplication {
public static void main(String[] args) {
SpringApplication.run(TestDupeApplication.class, args);
}
}
@Configuration
public class DemoClientRouter {
@Bean
public RouterFunction<ServerResponse> clientRoutes(DemoClientHandler requestHandler) {
return nest(path("/test"),
nest(accept(APPLICATION_JSON),
RouterFunctions.route(RequestPredicates.GET("/DemoClient"), requestHandler::getAll)
.andRoute(RequestPredicates.GET("/DemoClient/{id}"), requestHandler::getById)));
}
}
@Component
public class DemoClientHandler {
@Autowired
DemoClient demoClient;
public Mono<ServerResponse> getAll(ServerRequest request) {
System.out.println("DemoClientHandler.getAll( ServerRequest )");
System.out.println("DemoClientHandler.getAll() >> BEFORE invoking demoClient.getAll()");
Flux<String> fluxString = demoClient.getAll();
System.out.println("DemoClientHandler.getAll() >> AFTER invoking demoClient.getAll()");
return fluxString.hasElements().flatMap(hasElement -> {
return hasElement ? ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(fluxString, String.class)
: ServerResponse.noContent().build();
});
}
public Mono<ServerResponse> getById(ServerRequest request) {
System.out.println("DemoClientHandler.getById( ServerRequest )");
Mono<String> monoString;
return demoClient.getById( 2 ).flatMap(stringVal -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(Mono.just(stringVal), String.class))
.switchIfEmpty(ServerResponse.notFound().build());
}
}
@Component
public class DemoClient {
private final WebClient client;
public DemoClient() {
client = WebClient.create();
}
public Flux<String> getAll() {
System.out.println("DemoClient.getAll()");
Flux<String> fluxString;
fluxString = client.get().uri("http://localhost:8080/test/DemoMain")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMapMany(response -> response.bodyToFlux(String.class));
// fluxString = client.get().uri("http://localhost:8080/test/DemoMain")
// .accept(MediaType.APPLICATION_JSON)
// .retrieve()
// .bodyToFlux(String.class);
System.out.println("DemoClient.getAll() >> RETURN fluxString");
return fluxString;
}
public Mono<String> getById(int id) {
System.out.printf("DemoClient.getById( %d )%n", id);
return client.get().uri("http://localhost:8080/test/DemoMain/" + id)
.accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.bodyToMono(String.class));
}
}
@Configuration
public class DemoMainRouter {
@Bean
public RouterFunction<ServerResponse> demoPOJORoute(DemoMainHandler requestHandler) {
return nest(path("/test"),
nest(accept(APPLICATION_JSON),
RouterFunctions.route(RequestPredicates.GET("/DemoMain"), requestHandler::getAll)
.andRoute(RequestPredicates.GET("/DemoMain/{id}"), requestHandler::getById)));
}
}
@Component
public class DemoMainHandler {
public Mono<ServerResponse> getAll(ServerRequest request) {
System.out.println("DemoMainHandler.getAll( ServerRequest )");
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(Flux.just("Call", "Me", "Once"), String.class);
}
public Mono<ServerResponse> getById(ServerRequest request) {
System.out.println("DemoMainHandler.getById( ServerRequest )");
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(Mono.just("Only One"), String.class);
}
}
Этот код добавлен для поддержки последующего обсуждения ...
@Component
public class DemoClient {
private final WebClient client;
public DemoClient() {
client = WebClient.create();
}
public Flux<String> getAll() {
Flux<String> fluxString;
Mono<ClientResponse> monoCR = client.get().uri("http://localhost:8080/test/DemoMain")
.accept(MediaType.APPLICATION_JSON)
.exchange();
fluxString = monoCR.flatMapMany(clientResponse -> clientResponse.bodyToFlux(String.class));
// fluxString.subscribe();
// return fluxString;
return Flux.just("Foo", "Bar");
}