一、简述
调用 RestTemplate 的默认构造函数,RestTemplate 对象在底层通过使用 java.net 包下的实现创建 HTTP 请求,可以通过使用 ClientHttpRequestFactory 指定不同的HTTP请求方式。
使用的是spring5.0.1
默认使用 SimpleClientHttpRequestFactory,是 ClientHttpRequestFactory 实现类。如下流程:
1)使用默认构造方法new一个实例
RestTemplate template = new RestTemplate();
2)RestTemplate 内部通过调用 doExecute 方法,首先就是获取 ClientHttpRequest
doExcute核心处理
//通过ClientHttpRequestFactory工厂生产一个ClientHttpRequest
ClientHttpRequest request = this.createRequest(url, method);
if(requestCallback != null) {
//封装了请求头和请求体,使用了HttpMessageConverter
requestCallback.doWithRequest(request);
}
//ClientHttpRequest&execute 执行请求
response = request.execute();
//response error处理
this.handleResponse(url, method, response);
if(responseExtractor == null) {
resource = null;
return resource;
}
var14 = responseExtractor.extractData(response);
源代码
@Nullable
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback, @Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
Assert.notNull(url, "'url' must not be null");
Assert.notNull(method, "'method' must not be null");
ClientHttpResponse response = null;
Object var14;
try {
String resource;
try {
ClientHttpRequest request = this.createRequest(url, method);
if(requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
this.handleResponse(url, method, response);
if(responseExtractor == null) {
resource = null;
return resource;
}
var14 = responseExtractor.extractData(response);
} catch (IOException var12) {
resource = url.toString();
String query = url.getRawQuery();
resource = query != null?resource.substring(0, resource.indexOf(63)):resource;
throw new ResourceAccessException("I/O error on " + method.name() + " request for \"" + resource + "\": " + var12.getMessage(), var12);
}
} finally {
if(response != null) {
response.close();
}
}
return var14;
}
View Code
此处方法会调用第三步中HttpAccessor中的ClientHttpRequest createRequest方法。
3)RestTemplate 实现了抽象类 HttpAccessor ,所以可以调用父类的 createRequest
在HttpAccessor中
private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
public ClientHttpRequestFactory getRequestFactory() {
return this.requestFactory;
}
这里也提供了自定义的ClientHttpRequestFactory 请求工厂的方法,后续会使用到
public void setRequestFactory(ClientHttpRequestFactory requestFactory) {
Assert.notNull(requestFactory, "ClientHttpRequestFactory must not be null");
this.requestFactory = requestFactory;
}
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
ClientHttpRequest request = this.getRequestFactory().createRequest(url, method);
if(this.logger.isDebugEnabled()) {
this.logger.debug("Created " + method.name() + " request for \"" + url + "\"");
}
return request;
}
4)SimpleClientHttpRequestFactory 实现了 ClientHttpRequest,同时实现方法
注意 bufferRequestBody 是可以在 RestTemplate 设置,是标志是否使用缓存流的形式,默认是 true,缺点是当发送大量数据时,比如put/post的保存和修改,那么可能内存消耗严重。所以这时候可以设置 RestTemplate.setBufferRequestBody(false);
即使用 SimpleStreamingClientHttpRequest 来实现。
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
HttpURLConnection connection = this.openConnection(uri.toURL(), this.proxy);
this.prepareConnection(connection, httpMethod.name());
return (ClientHttpRequest)(this.bufferRequestBody?new SimpleBufferingClientHttpRequest(connection, this.outputStreaming):new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming));
}
第一行:创建java.net.HttpURLConnection
第二行:创建connection属性,同时设置了setDoInput
5)openConnection 即打开连接,而是 prepareConnection 各种连接准备,针对请求者
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
if(this.connectTimeout >= 0) {
connection.setConnectTimeout(this.connectTimeout);
}
if(this.readTimeout >= 0) {
connection.setReadTimeout(this.readTimeout);
}
connection.setDoInput(true);
if("GET".equals(httpMethod)) {
connection.setInstanceFollowRedirects(true);
} else {
connection.setInstanceFollowRedirects(false);
}
if(!"POST".equals(httpMethod) && !"PUT".equals(httpMethod) && !"PATCH".equals(httpMethod) && !"DELETE".equals(httpMethod)) {
connection.setDoOutput(false);
} else {
connection.setDoOutput(true);
}
connection.setRequestMethod(httpMethod);
}
5.1、设置连接超时时间:connectTimeout
5.2、设置读超时时间:readTimeout
5.3、设置URL允许输入:connection.setDoInput(true);//默认true
URL连接可用于输入和/或输出。 如果您打算使用URL连接进行输入,请将DoInput标志设置为true;否则,设置为false。 默认值是true。
httpUrlConnection.setDoInput(true);以后就可以使用conn.getInputStream().read();
5.4、setDoOutput
URL连接可用于输入和/或输出。 如果您打算将URL连接用于输出,请将DoOutput标志设置为true,如果不是,则为false 默认值是false。
如果是get请求
get请求用不到conn.getOutputStream(),因为参数直接追加在地址后面,因此默认是false。
如果是post、put、patch、delete
以上几种请求会将setDoOutput设置为true。
相当于可以使用:httpUrlConnection.setDoOutput(true);以后就可以使用conn.getOutputStream().write()
6、执行requestCallback.doWithRequest(request);
RequestCallback 封装了请求体和请求头对象,也就是说在该对象里面可以拿到我们需要的请求参数。
在执行 doWithRequest 时,有一个非常重要的步骤,他和前面Connection发送请求体有着密切关系,我们知道请求头就是 SimpleBufferingClientHttpRequest.addHeaders 方法,那么请求体 bufferedOutput 是如何赋值的呢?就是在 doWithRequest 里面,如下 StringHttpMessageConverter (其他 MessageConvert 也一样,这里也是经常乱码的原因)
由上文8.1、可知 RequestCallback 用于操作请求头和body,在请求发出前执行。以下是RequestCallback的 两个实现类
AcceptHeaderRequestCallback | 只处理请求头,用于getXXX()方法。 |
HttpEntityRequestCallback | 继承于AcceptHeaderRequestCallback可以处理请求头和body,用于putXXX()、postXXX()和exchange()方法。 |
其中HttpEntityRequestCallback有继承:HttpEntityRequestCallback extends RestTemplate.AcceptHeaderRequestCallback
故先查看下AcceptHeaderRequestCallback代码
public void doWithRequest(ClientHttpRequest request) throws IOException {
if(this.responseType != null) {
Class<?> responseClass = null;
if(this.responseType instanceof Class) {
responseClass = (Class)this.responseType;
}
List<MediaType> allSupportedMediaTypes = new ArrayList();
Iterator var4 = RestTemplate.this.getMessageConverters().iterator();//第一步
while(var4.hasNext()) {
HttpMessageConverter<?> converter = (HttpMessageConverter)var4.next();
if(responseClass != null) {
if(converter.canRead(responseClass, (MediaType)null)) {
allSupportedMediaTypes.addAll(this.getSupportedMediaTypes(converter));
}
} else if(converter instanceof GenericHttpMessageConverter) {
GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter)converter;
if(genericConverter.canRead(this.responseType, (Class)null, (MediaType)null)) {
allSupportedMediaTypes.addAll(this.getSupportedMediaTypes(converter));
}
}
}
if(!allSupportedMediaTypes.isEmpty()) {
MediaType.sortBySpecificity(allSupportedMediaTypes);
if(RestTemplate.this.logger.isDebugEnabled()) {
RestTemplate.this.logger.debug("Setting request Accept header to " + allSupportedMediaTypes);
}
request.getHeaders().setAccept(allSupportedMediaTypes);
}
}
}
注释第一步:获取RestTemplate所有绑定的MessageConverters。
有上文 8.2.2 RestTemplate初始化时绑定了基本的五种,以及其他更多,初始化代码
public RestTemplate() {
this.messageConverters = new ArrayList();
this.errorHandler = new DefaultResponseErrorHandler();
this.uriTemplateHandler = new DefaultUriBuilderFactory();
this.headersExtractor = new RestTemplate.HeadersExtractor();
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
this.messageConverters.add(new ResourceHttpMessageConverter(false));
this.messageConverters.add(new SourceHttpMessageConverter());
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
if(romePresent) {
this.messageConverters.add(new AtomFeedHttpMessageConverter());
this.messageConverters.add(new RssChannelHttpMessageConverter());
}
if(jackson2XmlPresent) {
this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
} else if(jaxb2Present) {
this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
if(jackson2Present) {
this.messageConverters.add(new MappingJackson2HttpMessageConverter());
} else if(gsonPresent) {
this.messageConverters.add(new GsonHttpMessageConverter());
} else if(jsonbPresent) {
this.messageConverters.add(new JsonbHttpMessageConverter());
}
if(jackson2SmilePresent) {
this.messageConverters.add(new MappingJackson2SmileHttpMessageConverter());
}
if(jackson2CborPresent) {
this.messageConverters.add(new MappingJackson2CborHttpMessageConverter());
}
}
View Code
此时可以分析一下StringHttpMessageConverter 代码
protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
if(this.writeAcceptCharset) {
outputMessage.getHeaders().setAcceptCharset(this.getAcceptedCharsets());
}
Charset charset = this.getContentTypeCharset(outputMessage.getHeaders().getContentType());
StreamUtils.copy(str, charset, outputMessage.getBody());
}
其中 str 就是请求体;HttpOutputMessage 对象就是我们准备的 ClientHttpRequest 对象,也就是上面的 SimpleBufferingClientHttpRequest extends AbstractBufferingClientHttpRequest
这样,先调用父类的流方法,把内容写入流中,然后调用父类的 executeInternal方法在调用自身的该方法 executeInternal ,如下一步
注意点1:charset 设置字符集,可以来自客户端;
public static final Charset DEFAULT_CHARSET;//静态代码块,即设置为ISO_8859_1,但是网络传输一般是UTF-8,故大部分情况需要设置编码
static {
DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;
}
注意点2:StreamUtils是spring中用于处理流的类,是java.io包中inputStream和outputStream,不是java8中Steam。使用时仅依赖spring-core
copy方法带三个参数:被拷贝的字符串,写文件时指定的字符集,指定目的地(outputStream)。
更多参看:
7)接着执行 response = request.execute();
调用接口ClientHttpRequest
public interface ClientHttpRequest extends HttpRequest, HttpOutputMessage {
ClientHttpResponse execute() throws IOException;
}
然后使用实例 SimpleBufferingClientHttpRequest 封装请求体和请求头
查看executeInternal
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
addHeaders(this.connection, headers);
if(HttpMethod.DELETE == this.getMethod() && bufferedOutput.length == 0) {
this.connection.setDoOutput(false);
}
if(this.connection.getDoOutput() && this.outputStreaming) {
this.connection.setFixedLengthStreamingMode(bufferedOutput.length);
}
this.connection.connect();
if(this.connection.getDoOutput()) {
FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());
} else {
this.connection.getResponseCode();
}
return new SimpleClientHttpResponse(this.connection);
}
delete 时通过请求方式和是否有请求体对象来判断是否需要发送请求体
如果是delete请求,首先设置 DoOutput = true,然后根据是否有请求体数据,然后封装请求体
FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());
8)解析response
接着就是 response 的解析了,主要还是 Error 的解析。
this.handleResponse(url, method, response);
内部处理多是解析error处理
protected void handleResponse(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
ResponseErrorHandler errorHandler = this.getErrorHandler();
boolean hasError = errorHandler.hasError(response);
if(this.logger.isDebugEnabled()) {
try {
this.logger.debug(method.name() + " request for \"" + url + "\" resulted in " + response.getRawStatusCode() + " (" + response.getStatusText() + ")" + (hasError?"; invoking error handler":""));
} catch (IOException var7) {
;
}
}
if(hasError) {
errorHandler.handleError(url, method, response);
}
}
View Code