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...
}
};
}

浙公网安备 33010602011771号