Spring Core 官方文档阅读笔记(十七)

1. UriComponentsBuilder

可以使用UriComponentsBuilder来构建一个uri,看例子:

UriComponents uriComponents = UriComponentsBuilder
                .fromUriString("http://example.com/hotels/{hotel}")
                .queryParam("q", "{q}")
                .encode()
                .build();

URI uri = uriComponents.expand("Model", "123").toUri();

也可以像下面这么写:

URI uri = UriComponentsBuilder.fromUriString("http://example.com/hotels/{hotel}")
                .queryParam("q", "{q}")
                .encode()
                .buildAndExpand("Model", "123")
                .toUri();

还可以这样:

URI uri = UriComponentsBuilder.fromUriString("http://example.com/hotels/{hotel}")
                .queryParam("q", "{q}")
                .encode()
                .build("Model", "123");

或者

RI uri = UriComponentsBuilder
        .fromUriString("http://example.com/hotels/{hotel}?q={q}")
        .build("Westin", "123");

2. UriBuilder

UriComponentsBuilder实现了UriBuilder,同样的,我们也可以通过实现UriBuilder来创建自己的UriBuilder。而UriBuilderFactory就提供了一个工厂类的模板,默认实现是DefaultUriBuilderFactory。比如,我们可以通过UriBuilderFactory来配置RestTemplate和WebClient(WebFlux),如下:

DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("http://example.com/hotels");
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.TEMPLATE_AND_VALUES);

RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);
String baseUrl = "http://example.com";
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);

URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
        .queryParam("q", "{q}")
        .build("Westin", "123");

3. UriEncoding

对URI进行编码,可以使用UriComponentsBuilder的encode方法或者UriComponents的encode方法。这两个方法的区别是,UriComponentsBuilder首先预编码URI模板,然后再扩展使用时严格编码URI,而UriComponents时扩展URI后再进行编码。

URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
            .queryParam("q", "{q}")
            .encode()
            .buildAndExpand("New York", "foo+bar")
            .toUri();

4. Servlet请求

也可以使用ServletUriComponentsBuilder相对于当前请求创建URI:

HttpServletRequest request = ...

// Re-uses host, scheme, port, path and query string...

ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request)
        .replaceQueryParam("accountId", "{id}").build()
        .expand("123")
        .encode();
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromContextPath(request)
        .path("/accounts").build()
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request)
        .path("/accounts").build()

5. Controller请求

可以从Controller中获取对应的URI:

UriComponents uriComponents = MvcUriComponentsBuilder
    .fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();

或者

UriComponents uriComponents = MvcUriComponentsBuilder
    .fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();

上述两种方法可以满足大多数的需求,但是如果你要获取的请求并不在Servlet上下文中,或者需要加入前缀,就需要另一种方式:

UriComponentsBuilder base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en");
MvcUriComponentsBuilder builder = MvcUriComponentsBuilder.relativeTo(base);
builder.withMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();

6. DeferredResult

异步请求的一个特点是,可以允许Controller的方法把请求的返回值包在DeferredResult中:

@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
    DeferredResult<String> deferredResult = new DeferredResult<String>();
    // Save the deferredResult somewhere..
    return deferredResult;
}

// From some other thread...
deferredResult.setResult(data);

这个使用的场景还是很多的,比如有A和B两个请求,A需要先发起,然后仅当B在规定的时间内结束才返回成功,此时,就可以在A请求中设置:

deferredResult.onTimeout(() -> {
    ...
})

而在B处理中执行setResult操作。

详细过程可以参考网上的博文:DEFERREDRESULT使用方式和场景

整体的处理过程是:

  • Controller返回一个DefferedResult,并且将它存储在内存中的队列或者链表中,以供后面访问。
  • Spring MVC调用request.startAsync()。
  • DispatcherServlet和所有配置的过滤器退出请求处理线程,但响应保持打开。
  • 应用在其他线程中设置DefferedResult,Spring MVC将请求调度回Servlet容器。
  • DispatcherServlet被重新调用,并继续处理异步生成的返回值。

7. Callable

也可以通过使用Callable来包装返回值,达到异步请求的效果:

@PostMapping
public Callable<String> processUpload(final MultipartFile file) {

    return new Callable<String>() {
        public String call() throws Exception {
            // ...
            return "someView";
        }
    };

}

整体的处理过程是:

  • Controller返回一个Callable。
  • Spring MVC调用request.startAsync(),并提交返回的Callable到TaskExecutor中,以便在单独的线程中进行处理。
  • DispatcherServlet和所有配置的过滤器退出请求处理线程,但响应保持打开。
  • Callable产生一个结果,Spring MVC将请求调度回Servlet容器以完成处理。
  • DispatcherServlet被重新调用,并继续处理异步生成的返回值。

8. Proccessing

可以通过request.startAsync()将Servlet设置为异步模式。而当处理中发生异常时,可以选则使用setResult或者setErrorResult来处理。在Callable中,同样可以选择返回或抛出异常。

HandlerInterceptor实例可以是AsyncHandlerInterceptor类型,以便在启动异步处理的初始请求上接收AfterConcurrentHandlingStarted回调(而不是postHandle和AfterCompletion)。

HandlerInterceptor实现还可以注册CallableProcessingInterceptor或DeferredResultProcessingInterceptor,以便更深入地与异步请求的生命周期集成(例如,处理超时事件)。详细信息请参见AsyncHandlerInterceptor。

DeferredResult提供onTimeout(Runnable)和onCompletion(Runnable)回调。更多细节见DeferredResult的javadoc。Callable可以替代公开超时和完成回调的其他方法的WebAsyncTask。

9. Http Streaming

我们上面通过DefferedResult和Callable来处理单个异步返回值,而如果我们需要处理多个异步返回值的话,就需要用到HttpStreaming相关的功能。

  • Object
    可以使用ResponseBodyEmitter返回值生成对象流,每个对象都使用HttpMessageConverter序列化并写入响应。看下官方的例子:
@GetMapping("/events")
public ResponseBodyEmitter handle() {
    ResponseBodyEmitter emitter = new ResponseBodyEmitter();
    // Save the emitter somewhere..
    return emitter;
}

// In some other thread
emitter.send("Hello once");

// and again later on
emitter.send("Hello again");

// and done at some point
emitter.complete();

具体用法其实和DefferedResult差不多,不一样的是,当调用emitter.send的时候,是持续往response中写入东西的,也就是连接并没有关闭,只有当调用emitter.complete方法时,response才算结束。

ResponseBodyEmitter同样有onCompletion、onError和onTimeout方法。

当emitter抛出IOException时(例如,如果远程客户端离开了),应用程序不负责清理连接,也不应调用emitter.complete或emitter.complete WithError。相反,servlet容器自动启动AsyncListener错误通知,Spring MVC在其中进行completeWithError调用。此调用进而执行对应用程序的最后一次ASYNC分派,在此期间,Spring MVC调用配置的异常解析器并完成请求。

  • SSE
    SseEmitter是ResponseBodyEmitter的一个子类,提供服务器事件发送功能。还是先看官方例子:
@GetMapping(path="/events", produces=MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter handle() {
    SseEmitter emitter = new SseEmitter();
    // Save the emitter somewhere..
    return emitter;
}

// In some other thread
emitter.send("Hello once");

// and again later on
emitter.send("Hello again");

// and done at some point
emitter.complete();

用法基本和ResponseBodyEmitter一致。

使用方法可以参照:使用SseEmitter不断向网页输出结果

  • Raw Data
    我们可以使用StreamingResponseBody来方便的完成文件下载功能,看例子:
@GetMapping("/download")
public StreamingResponseBody handle() {
    return new StreamingResponseBody() {
        @Override
        public void writeTo(OutputStream outputStream) throws IOException {
            // write...
        }
    };
}

可以参照:Spring MVC Streaming and SSE Request Processing

posted @ 2020-05-22 10:21  kuromaru  阅读(643)  评论(0)    收藏  举报