【问题标题】:WebClient is not successfully invoking "POST" operationWebClient 未成功调用“POST”操作
【发布时间】:2019-12-13 11:29:17
【问题描述】:

我正在玩 Spring 的 WebClient。 REST 端点的主要实现(在 DemoPOJORouterDemoPOJOHandler 中)似乎有效。此外,DemoClientRouterDemoClientHandler 中的 http.Get 端点似乎可以工作。

但是,DemoClient 实现的 http.Post“什么都不做”。它返回成功 (200),但没有任何内容添加到虚拟存储库中。我有一种感觉,我需要在 DemoClient 中做一些事情以使 DemoPOJOHandler 中的 http.Post 端点实际执行(即,我不相信 中的语句DemoPOJOService.add()DemoPOJORepo.add() 正在执行)。

基于之前在 WebFlux/reactive/functional 工作中的错误,我感觉我没有成功订阅,因此这些语句永远不会被调用。但是,我很难确定“为什么”。

测试代码如下...

DemoClient 路由器...

@Configuration
public class DemoClientRouter {

    @Bean
    public RouterFunction<ServerResponse> clientRoutes(DemoClientHandler requestHandler) {
        return nest(path("/v2"),
                nest(accept(APPLICATION_JSON),
                        RouterFunctions.route(RequestPredicates.GET("/DemoClient/{id}"), requestHandler::getById)
                                       .andRoute(RequestPredicates.POST("/DemoClient"), requestHandler::add)));
    }
}

DemoClient 处理程序...

@Component
public class DemoClientHandler {

    public static final String PATH_VAR_ID = "id";

    @Autowired
    DemoClient demoClient;

    public Mono<ServerResponse> getById(ServerRequest request) {
        Mono<DemoPOJO> monoDemoPOJO;
        int            id;

        // short-circuit if bad request or invalid value for id
        id = getIdFromServerRequest(request);
        if (id < 1) {
            return ServerResponse.badRequest().build();
        }

        // non-blocking mechanism for either returning the Mono<DemoPOJO>
        //  or an empty response if Mono<Void> was returned by repo.getById()
        return demoClient.getById(id).flatMap(demoPOJO -> ServerResponse.ok()
                                                                        .contentType(MediaType.APPLICATION_JSON)
                                                                        .body(Mono.just(demoPOJO), DemoPOJO.class))
                                                                        .switchIfEmpty(ServerResponse.notFound().build());
    }

    public Mono<ServerResponse> add(ServerRequest request) {
        return request.bodyToMono(DemoPOJO.class).doOnSuccess( demoPOJO -> demoClient.add(demoPOJO))
                                                     .then(ServerResponse.ok().build())
                                                 .onErrorResume(e -> simpleErrorReporter(e))
                                                 .switchIfEmpty(ServerResponse.badRequest().build());
    }

    private int getIdFromServerRequest(ServerRequest request) {
        Map<String, String> pathVariables = request.pathVariables();
        int                 id            = -1;

        // short-circuit if bad request
        //  should never happen, but if this method is ever called directly (vice via DemoPOJORouter)
        if ((pathVariables == null)
         || (!pathVariables.containsKey(PATH_VAR_ID))) {
            return id;
        }

        try {
            id = Integer.parseInt(pathVariables.get(PATH_VAR_ID));
        } catch (NumberFormatException e) {
            // swallow the error, return value <0 to signal error
            id = -1;
        }
        return id;
    }

    private Mono<ServerResponse> simpleErrorReporter(Throwable e) {
        return ServerResponse.badRequest()
                             .contentType(MediaType.TEXT_PLAIN)
                             .syncBody(e.getMessage());
    }
}

DemoClient 实现...

@Component
public class DemoClient {

    private final WebClient client;

    public DemoClient() {
        client = WebClient.create();
    }

    public Mono<DemoPOJO> getById(int id) {
        return client.get().uri("http://localhost:8080/v2/DemoPOJO/" + id)
                           .accept(MediaType.APPLICATION_JSON)
                           .exchange()
                           .flatMap(response -> response.bodyToMono(DemoPOJO.class));
    }

    public Mono<Boolean> add(DemoPOJO demoPOJO) {
        return client.post().uri("http://localhost:8080/v2/DemoPOJO")
                            .syncBody(demoPOJO)
                            .exchange()
                            .flatMap(response -> response.bodyToMono(Boolean.class));
    }
}

还有 DemoPOJO 的东西,从 DemoPOJORouter 开始...

@Configuration
public class DemoPOJORouter {

    @Bean
    public RouterFunction<ServerResponse> demoPOJORoute(DemoPOJOHandler requestHandler) {
        return nest(path("/v2"),
                nest(accept(APPLICATION_JSON),
                        RouterFunctions.route(RequestPredicates.GET("/DemoPOJO/{id}"), requestHandler::getById)
                                       .andRoute(RequestPredicates.POST("/DemoPOJO"), requestHandler::add)));
    }
}

DemoPOJOHandler...

@Component
public class DemoPOJOHandler {

    public static final String PATH_VAR_ID = "id";

    @Autowired
    private DemoPOJOService service;

    public Mono<ServerResponse> getById(ServerRequest request) {
        Mono<DemoPOJO> monoDemoPOJO;
        int            id;

        // short-circuit if bad request or invalid value for id
        id = getIdFromServerRequest(request);
        if (id < 1) {
            return ServerResponse.badRequest().build();
        }

        // non-blocking mechanism for either returning the Mono<DemoPOJO>
        //  or an empty response if Mono<Void> was returned by repo.getById()
        return service.getById(id).flatMap(demoPOJO -> ServerResponse.ok()
                                                                     .contentType(MediaType.APPLICATION_JSON)
                                                                     .body(Mono.just(demoPOJO), DemoPOJO.class))
                                  .switchIfEmpty(ServerResponse.notFound().build());
    }

    public Mono<ServerResponse> add(ServerRequest request) {
        return request.bodyToMono(DemoPOJO.class).doOnSuccess( demoPOJO -> service.add(demoPOJO))
                                                     .then(ServerResponse.ok().build())
                                                 .onErrorResume(e -> simpleErrorReporter(e))
                                                 .switchIfEmpty(ServerResponse.badRequest().build());
    }

    private int getIdFromServerRequest(ServerRequest request) {
        Map<String, String> pathVariables = request.pathVariables();
        int                 id            = -1;

        // short-circuit if bad request
        //  should never happen, but if this method is ever called directly (vice via DemoPOJORouter)
        if ((pathVariables == null)
         || (!pathVariables.containsKey(PATH_VAR_ID))) {
            return id;
        }

        try {
            id = Integer.parseInt(pathVariables.get(PATH_VAR_ID));
        } catch (NumberFormatException e) {
            // swallow the exception, return illegal value to signal error
            id = -1;
        }
        return id;
    }

    private Mono<ServerResponse> simpleErrorReporter(Throwable e) {
        return ServerResponse.badRequest()
                             .contentType(MediaType.TEXT_PLAIN)
                             .syncBody(e.getMessage());
    }
}

DemoPOJOService...

@Component
public class DemoPOJOService {

    @Autowired
    private DemoPOJORepo demoPOJORepo;

    public Mono<DemoPOJO> getById(int id) {
        DemoPOJO demoPOJO = demoPOJORepo.getById(id);

        return (demoPOJO == null) ? Mono.empty()
                                  : Mono.just(demoPOJO);
    }

    public Mono<Boolean> add(DemoPOJO demoPOJO) {
        return Mono.just(demoPOJORepo.add(demoPOJO));
    }
}

DemoPOJORepo...

@Component
public class DemoPOJORepo {

    private static final int NUM_OBJS = 5;

    private static DemoPOJORepo demoRepo = null;

    private Map<Integer, DemoPOJO> demoPOJOMap;

    private DemoPOJORepo() {
        initMap();
    }

    public static DemoPOJORepo getInstance() {
        if (demoRepo == null) {
            demoRepo = new DemoPOJORepo();
        }
        return demoRepo;
    }

    public DemoPOJO getById(int id) {
        return demoPOJOMap.get(id);
    }

    public boolean add(DemoPOJO demoPOJO) throws InvalidParameterException {
        // short-circuit on null pointer or duplicate id
        if (demoPOJO == null) {
            throw new InvalidParameterException("Add failed, null object detected...");
        } else if (demoPOJOMap.containsKey(demoPOJO.getId())) {
            throw new InvalidParameterException("Add failed, duplicate id detected...");
        }

        demoPOJOMap.put(demoPOJO.getId(), demoPOJO);
        // if the return statement is reached, then the new demoPOJO was added
        return true;
    }
}

最后,DemoPOJO...

public class DemoPOJO {

    public static final String DEF_NAME = "DEFAULT NAME";
    public static final int DEF_VALUE = 99;

    private int id;
    private String name;
    private int value;

    public DemoPOJO(int id) {
        this(id, DEF_NAME, DEF_VALUE);
    }

    public DemoPOJO(@JsonProperty("id") int id, @JsonProperty("name") String name, @JsonProperty("value") int value) {
        this.id = id;
        this.name = name;
        this.value = value;
    }

    /*
     * setters and getters go here
     */

    public String toString() {
        StringBuilder builder = new StringBuilder();

        builder.append(id);
        builder.append(" :: ");
        builder.append(name);
        builder.append(" :: ");
        builder.append(value);
        return builder.toString();
    }
}

【问题讨论】:

标签: spring-boot spring-webflux project-reactor


【解决方案1】:

这可能是你的问题。

DemoPOJOHandler.class

request.bodyToMono(DemoPOJO.class).doOnSuccess(demoPOJO -> service.add(demoPOJO))

DemoPOJOService.class

public Mono<Boolean> add(DemoPOJO demoPOJO) {
    return Mono.just(demoPOJORepo.add(demoPOJO));
}

doOnSuccess 返回Void,但您调用的方法将“动作”包装在返回的Mono 中。所以demoPOJORepo#add函数永远不会被触发,因为你已经破坏了这里的事件链。最简单的解决方法是删除包装 Mono 并返回 void。

public void add(DemoPOJO demoPOJO) {
    demoPOJORepo.add(demoPOJO);
}

这花了我很长时间才找到,所以这里有一些提问时的提示。

  • 你的类的名字太像了,很难遵循代码流
  • DemoPOJOService service 你的名字太像了,所以当我看到service 时,是 DemoPOJOService 还是 DemoClientService?请明确名称。
  • 当你写我不知道你在说什么时,没有什么叫做 http.POST
  • 您在使用 POST 部分时遇到了问题,但您发布了所有内容,即使是正常工作的 GET 部分,请仅发布您怀疑相关且属于问题的一部分的代码。
  • 更清楚地解释问题,你做了什么,你是怎么做的,你的应用程序结构是什么等等
  • 您的端点网址什么也没说“/DemoClient”?

如何才能更清楚地提出这个问题:

我在同一个弹簧反应的两个路由器中有两个端点 应用。

当我向“/add”端点发出 POST 请求时,该端点依次 使用 WebClient 对同一应用程序进行 POST 调用 另一个名为“/addToMap”的端点。

当第一次调用返回时,它返回 200 OK 状态,但是当我 检查地图(第二个端点应该添加发布的 data to) 没有添加任何内容。

所以请,下次问问题时,要清楚,非常清楚,比你想象的要清楚得多。确保您的代码也清晰,具有良好的变量和类名称以及清晰的 url 名称。如果您自己的计算机上的名称杂乱无章,但在此处发布时请保持礼貌并清理代码。为类和参数添加好名称需要 5 分钟,以便我们更快地理解您的代码。

请花点时间阅读“如何提出一个好问题”。

How to ask a good question

【讨论】:

  • 您的回复有点帮助,但没有纠正这种行为。我已经修改了我的问题和支持代码,将您的 cmets 牢记在心,以尝试更好地定义/本地化我的问题。问题似乎在于 DemoClient.add()DemoPOJOHandler.add() 之间的切换。
  • 还是很难/v2/DemoPOJO/v2/DemoClient/DemoPOJO?
  • 你在做同样的错误 ".doOnSuccess( demoPOJO -> demoClient.add(demoPOJO))" add 函数返回一个 Mono&lt;Void&gt;doOnSuccess 接受一个消费者,一个非反应函数。一个不返回单声道的函数。一个只返回 void 的函数所以你又在这里打破了链条。
猜你喜欢
  • 2015-01-09
  • 2018-06-15
  • 2018-09-14
  • 1970-01-01
  • 2018-10-23
  • 1970-01-01
  • 2018-08-04
  • 1970-01-01
  • 2014-12-14
相关资源
最近更新 更多