Spring MVC + DeferredResult add Hateoas stuff

醉酒当歌 提交于 2020-01-02 04:46:10

问题


For the rest interface the Spring MVC + RxJava + DeferredResult returned from controllers is used.

I am thinking about adding Hateoas support to the endpoints. The natural choice would be the Spring Hateoas. The problem is that Spring Hateoas would not work in the asynchronous/multi-threading environment since it uses ThreadLocal.

Is there any way to workaround that constraint? I do not think so but maybe someone has any suggestions.

Has anyone used other APIs to add Hateoas support to the rest endpoints?

Thank you.


回答1:


So the solution I've used is to closure in the request attributes and then apply them as part of a lift operator

public class RequestContextStashOperator<T> implements Observable.Operator<T, T> {

    private final RequestAttributes attributes;

    /**
     * Spring hateoas requires the request context to be set but observables may happen on other threads
     * This operator will reapply the context of the constructing thread on the execution thread of the subscriber
     */
    public RequestContextStashOperator() {
        attributes = RequestContextHolder.currentRequestAttributes();
    }
    @Override
    public Subscriber<? super T> call(Subscriber<? super T> subscriber) {
        return new Subscriber<T>() {
            @Override
            public void onCompleted() {
                subscriber.onCompleted();
            }

            @Override
            public void onError(Throwable e) {
                subscriber.onError(e);
            }

            @Override
            public void onNext(T t) {
                RequestContextHolder.setRequestAttributes(attributes);
                subscriber.onNext(t);
            }
        };
    }
}

which you can then use on an observable like

lift(new RequestContextStashOperator<>())

as long as the object is created in the same thread as the request. You can then use a map after in the observable chain to map your object up to being a resource and add your hateoas links in.




回答2:


So answer is a bit late, but probably someone will find it useful. You are right about ThreadLocal - if you generate hateoas links in different thread, then it fails with exception. I found some kind of workaround for this:

@RequestMapping(path = "/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
DeferredResult<ResponseEntity<ProductResource>> example(@PathVariable("id") final String productId, final HttpServletRequest request) {

    final DeferredResult<ResponseEntity<ProductResource>> deferredResult = new DeferredResult<>();

    request.setAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE, request.getContextPath());
    final RequestAttributes requestAttributes = new ServletRequestAttributes(request);

    productByIdQuery.byId(UUID.fromString(productId)).subscribe(productEntity -> {
        RequestContextHolder.setRequestAttributes(requestAttributes);
            deferredResult.setResult(result, HttpStatus.OK))
    }, deferredResult::setErrorResult);

    return deferredResult;
}

So as you see, I save RequestAttributes so I can set them later in the callback. This solves just part of the problem - you'll get another exception because you'll loose contextPath attribute. To avoid this save it explicitly:

request.setAttribute(WebUtils.INCLUDE_CONTEXT_PATH_ATTRIBUTE, request.getContextPath());

After those changes everything seems to work, but looks messy of course. I hope that somebody can provide more elegant solution.



来源:https://stackoverflow.com/questions/28540421/spring-mvc-deferredresult-add-hateoas-stuff

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!