怎么让spring boot mvc理解observables?

时间:2016-10-20 13:28:21

标签: java spring spring-mvc spring-boot rx-java

我想从spring的mvc控制器返回Observable。它适用于Single

@GetMapping Object a() {return Single.just(1);}

正如预期的那样,当我查询服务器时,我得到1。但是当我对Observable执行相同的操作时:

@GetMapping Object a() {return Observable.just(1);}

我得到的答案是{}。 spring-mvc没有订阅返回的Observable,只是将它序列化为json。 spring-mvc可以理解Observable开箱即用,我只是搞砸了一些配置?或者我是否必须注册我的自定义处理程序或安装一些插件?

4 个答案:

答案 0 :(得分:3)

您可以使用Spring MVC Reactive(但目前尚未作为最终版本发布)。它适用于Reactor和RxJava。你将能够编写这种控制器:

 @Test
 class ExampleController {
         @RequestMapping("/hello")
         public Single<String> hello() { return Single.just("world"); }
 }

或者您可以编写自己的类适配器并将Single转换为Spring DeferredResult(请参阅此example

此示例来自您可能希望直接使用的Spring Boot Starter

答案 1 :(得分:0)

spring-cloud-netflix中有一个返回值处理程序。 pom依赖项下面对我有用。

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-netflix-core</artifactId>
    </dependency>

我已使用rx 1.x

对此进行了测试
    <dependency>
        <groupId>io.reactivex</groupId>
        <artifactId>rxjava</artifactId>
        <version>1.1.10</version>
    </dependency>

答案 2 :(得分:0)

如果您无法升级到弹簧反应式,则可以使用org.springframework.web.context.request.async.DeferredResult

public class ExampleController {

    @RequestMapping("/hello")
    public DeferredResult<ResponseEntity<String>> hello() {
        DeferredResult<ResponseEntity<String>> deferredResult = new DeferredResult<>();

        // your observable
        Observable.just("world")
              .subscribe(
                      text -> deferredResult.setResult(
                                    ResponseEntity.accepted()
                                                  .contentType(MediaType.TEXT_PLAIN)
                                                  .body(text)),
                      error -> deferredResult.setResult(
                                    ResponseEntity.status(HttpStatus.I_AM_A_TEAPOT)
                                                  .build()
              );

        return deferredResult;
    }

}

答案 3 :(得分:0)

如果您使用的是Springboot 1.5 并想从restControllers返回Observable 并避免此消息: “找不到类型为io.reactivex.internal.operators.observable.ObservableMap的返回值的转换器” 您必须添加两个班级

@EnableWebMvc
@Configuration
@ComponentScan(basePackages = {"PACKAGE CONTROLLER"})
public class DispatcherContextConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
        returnValueHandlers.add(new ObservableReturnValueHandler());
    }
}

public class ObservableReturnValueHandler implements AsyncHandlerMethodReturnValueHandler {

    @Override
    public boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType) {
        return returnValue != null && supportsReturnType(returnType);
    }

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return Observable.class.isAssignableFrom(returnType.getParameterType());
    }

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest) throws Exception {

        if (returnValue == null) {
            mavContainer.setRequestHandled(true);
            return;
        }

        final Observable<?> observable = Observable.class.cast(returnValue);
        WebAsyncUtils.getAsyncManager(webRequest)
            .startDeferredResultProcessing(new ObservableAdapter<>(observable), mavContainer);
    }

    public class ObservableAdapter<T> extends DeferredResult<T> {
        public ObservableAdapter(Observable<T> observable) {
            observable.subscribe(this::setResult, this::setErrorResult);
        }
    }
}

然后

 @GetMapping(path = "{idDocument}/ropObs")
    public Observable<DocumentDto> getDocumentRopObs(@PathVariable String idDocument) {

    DocumentDto r = documentService.getDocumentRopInfo(idDocument)
        .map(dtoMapper::documentBusinessToDto)
        .doOnError(error -> {throw new ApiErrorServerError(error.getMessage());})
        .blockingSingle();
        return Observable.just(r);
    }

和更好的做法

@GetMapping(path = "{idDocument}/ropObs2")
    public Observable<DocumentDto> getDocumentRopObs2(@PathVariable String idDocument) {

      return documentService.getDocumentRopInfo(idDocument).map(dtoMapper::documentBusinessToDto);
    }