RestTemplate源码解析
前言
RestTemplate进行http相关的请求的最底层的实现是利用的java原生的api java.net.URLConnection等实现的,关于如何实现的可以看上一篇文章中的demo。了解了java原生的http请求对解析RestTemplate源码很有帮助。
核心doExecute方法
无论用RestTemplate进行get、post、delete、put等http请求他都要走各种execute重载方法,而任何execute方法都会走doExecute这个方法,看下他的源码
/** * Execute the given method on the provided URI. * <p>The {@link ClientHttpRequest} is processed using the {@link RequestCallback}; * the response with the {@link ResponseExtractor}. * @param url the fully-expanded URL to connect to * @param method the HTTP method to execute (GET, POST, etc.) * @param requestCallback object that prepares the request (can be {@code null}) * @param responseExtractor object that extracts the return value from the response (can be {@code null}) * @return an arbitrary object, as returned by the {@link ResponseExtractor} */ protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback, ResponseExtractor<T> responseExtractor) throws RestClientException { Assert.notNull(url, "'url' must not be null"); Assert.notNull(method, "'method' must not be null"); ClientHttpResponse response = null; try { ClientHttpRequest request = createRequest(url, method); if (requestCallback != null) { requestCallback.doWithRequest(request); } response = request.execute(); handleResponse(url, method, response); if (responseExtractor != null) { return responseExtractor.extractData(response); } else { return null; } } catch (IOException ex) { String resource = url.toString(); String query = url.getRawQuery(); resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource); throw new ResourceAccessException("I/O error on " + method.name() + " request for \"" + resource + "\": " + ex.getMessage(), ex); } finally { if (response != null) { response.close(); } } }
分别解释下各个参数的意义:
URI url:请求的http地址,会利用url.openConnection()来链接。
HttpMethod method:这个就比较简单了,代表http请求的方法,GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE。
RequestCallback requestCallback:这个会在进行http请求前处理好http请求的head,如果是post这种带body的请求也会做好body的处理。常见的中文乱码的问题就是在这发生的,当然,涉及到的类不止他一个。
ResponseExtractor<T> responseExtractor:这个是用来处理http的response的,当我们直接通过log.error("http response error,{}",e)打印出来的日志不是我们想要的完整日志,就是这边捣的鬼。
因为doExecute方法实际也没几行,所以我们一行行分析;
(一)ClientHttpRequest request = createRequest(url, method);
/** * Create a new {@link ClientHttpRequest} via this template's {@link ClientHttpRequestFactory}. * @param url the URL to connect to * @param method the HTTP method to execute (GET, POST, etc) * @return the created request * @throws IOException in case of I/O errors * @see #getRequestFactory() * @see ClientHttpRequestFactory#createRequest(URI, HttpMethod) */ protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException { ClientHttpRequest request = getRequestFactory().createRequest(url, method); if (logger.isDebugEnabled()) { logger.debug("Created " + method.name() + " request for \"" + url + "\""); } return request; }
他的注释是这样的Create a new {@link ClientHttpRequest} via this template's {@link ClientHttpRequestFactory}。这个很好理解,通过ClientHttpRequestFactory获取了一个ClientHttpRequest的实例。
首先是
public ClientHttpRequestFactory getRequestFactory() { return this.requestFactory; }
this.requestFactory就是取得全局变量 private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();这边可以看到默认的是SimpleClientHttpRequestFactory,那么如果我想换个ClientHttpRequestFactory呢,答案就是在创建RestTemplate实例时指定ClientHttpRequestFactory,当然这个是在配置时做。
@Bean public RestTemplate restTemplate(ClientHttpRequestFactory factory) { return new RestTemplate(factory); }
这边我们以SimpleClientHttpRequestFactory继续往下走。
@Override public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { HttpURLConnection connection = openConnection(uri.toURL(), this.proxy); prepareConnection(connection, httpMethod.name()); if (this.bufferRequestBody) { return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming); } else { return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming); } }
HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);这一步就是执行了url.openConnection(),
prepareConnection(connection, httpMethod.name());这一步设置了http请求的方法,同时判断请求的方法做一些java原生态api需要做的设置,具体如下:
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(true); }else { connection.setDoOutput(false); }
最后是生成并返回了一个ClientHttpRequest实例,
if (this.bufferRequestBody) { return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);//如果是默认的SimpleClientHttpRequestFactory,那么返回的就是这个了 } else { return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming); }
(二)requestCallback.doWithRequest(request);
RequestCallback的实现类一般用到的就是定义在RestTemplate中的HttpEntityRequestCallback和AcceptHeaderRequestCallback,其中HttpEntityRequestCallback extends AcceptHeaderRequestCallback
既然HttpEntityRequestCallback继承了AcceptHeaderRequestCallback,那么比AcceptHeaderRequestCallback多做了什么呢?答案就是:HttpEntityRequestCallback多做了关于http请求body的处理。
首先我们先看看AcceptHeaderRequestCallback的doWithRequest方法
@Override 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<MediaType>(); for (HttpMessageConverter<?> converter : getMessageConverters()) { if (responseClass != null) { if (converter.canRead(responseClass, null)) { allSupportedMediaTypes.addAll(getSupportedMediaTypes(converter)); } } else if (converter instanceof GenericHttpMessageConverter) { GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter; if (genericConverter.canRead(this.responseType, null, null)) { allSupportedMediaTypes.addAll(getSupportedMediaTypes(converter)); } } } if (!allSupportedMediaTypes.isEmpty()) { MediaType.sortBySpecificity(allSupportedMediaTypes); if (logger.isDebugEnabled()) { logger.debug("Setting request Accept header to " + allSupportedMediaTypes); } request.getHeaders().setAccept(allSupportedMediaTypes); } } }
这里面有个for循环for (HttpMessageConverter<?> converter : getMessageConverters()),getMessageConverters()是这样子的
public List<HttpMessageConverter<?>> getMessageConverters() { return this.messageConverters; }
this.messageConverters是这样子的
private final List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
那么messageConverters里面的值是什么呢?答案是,RestTemplate实例化时初始化了messageConverters的值
public RestTemplate() { this.messageConverters.add(new ByteArrayHttpMessageConverter()); this.messageConverters.add(new StringHttpMessageConverter()); this.messageConverters.add(new ResourceHttpMessageConverter()); this.messageConverters.add(new SourceHttpMessageConverter<Source>()); 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()); } }
那些if判断其实就是看项目中有没有引入这些jar
private static boolean romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", RestTemplate.class.getClassLoader()); private static final boolean jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", RestTemplate.class.getClassLoader()); private static final boolean jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", RestTemplate.class.getClassLoader()) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", RestTemplate.class.getClassLoader()); private static final boolean jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", RestTemplate.class.getClassLoader()); private static final boolean gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", RestTemplate.class.getClassLoader());
下面就是调用converter的canRead方法
canRead方法其实就是判断这个converter是否支持对这种类型进行解析。
然后拿出这些converter支持的MediaType
allSupportedMediaTypes.addAll(getSupportedMediaTypes(converter));
最后将这些MediaType设置到head中去
request.getHeaders().setAccept(allSupportedMediaTypes);
以上是适用于没有body的请求,如果有body的请求,会走HttpEntityRequestCallback,他继承自AcceptHeaderRequestCallback,多做了关于body的处理
public void doWithRequest(ClientHttpRequest httpRequest) throws IOException { super.doWithRequest(httpRequest); if (!this.requestEntity.hasBody()) { HttpHeaders httpHeaders = httpRequest.getHeaders(); HttpHeaders requestHeaders = this.requestEntity.getHeaders(); if (!requestHeaders.isEmpty()) { for (Map.Entry<String, List<String>> entry : requestHeaders.entrySet()) { httpHeaders.put(entry.getKey(), new LinkedList<String>(entry.getValue())); } } if (httpHeaders.getContentLength() < 0) { httpHeaders.setContentLength(0L); } } else { Object requestBody = this.requestEntity.getBody(); Class<?> requestBodyClass = requestBody.getClass(); Type requestBodyType = (this.requestEntity instanceof RequestEntity ? ((RequestEntity<?>)this.requestEntity).getType() : requestBodyClass); HttpHeaders httpHeaders = httpRequest.getHeaders(); HttpHeaders requestHeaders = this.requestEntity.getHeaders(); MediaType requestContentType = requestHeaders.getContentType(); for (HttpMessageConverter<?> messageConverter : getMessageConverters()) { if (messageConverter instanceof GenericHttpMessageConverter) { GenericHttpMessageConverter<Object> genericMessageConverter = (GenericHttpMessageConverter<Object>) messageConverter; if (genericMessageConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) { if (!requestHeaders.isEmpty()) { for (Map.Entry<String, List<String>> entry : requestHeaders.entrySet()) { httpHeaders.put(entry.getKey(), new LinkedList<String>(entry.getValue())); } } if (logger.isDebugEnabled()) { if (requestContentType != null) { logger.debug("Writing [" + requestBody + "] as \"" + requestContentType + "\" using [" + messageConverter + "]"); } else { logger.debug("Writing [" + requestBody + "] using [" + messageConverter + "]"); } } genericMessageConverter.write( requestBody, requestBodyType, requestContentType, httpRequest); return; } } else if (messageConverter.canWrite(requestBodyClass, requestContentType)) { if (!requestHeaders.isEmpty()) { for (Map.Entry<String, List<String>> entry : requestHeaders.entrySet()) { httpHeaders.put(entry.getKey(), new LinkedList<String>(entry.getValue())); } } if (logger.isDebugEnabled()) { if (requestContentType != null) { logger.debug("Writing [" + requestBody + "] as \"" + requestContentType + "\" using [" + messageConverter + "]"); } else { logger.debug("Writing [" + requestBody + "] using [" + messageConverter + "]"); } } ((HttpMessageConverter<Object>) messageConverter).write( requestBody, requestContentType, httpRequest); return; } } String message = "Could not write request: no suitable HttpMessageConverter found for request type [" + requestBodyClass.getName() + "]"; if (requestContentType != null) { message += " and content type [" + requestContentType + "]"; } throw new RestClientException(message); } }
这边也是先循环converter,然后选出能处理的converter,然后写入到body
((HttpMessageConverter<Object>) messageConverter).write(
requestBody, requestContentType, httpRequest);
这边以StringHttpMessageConverter举例,调用writeInternal方法
protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException { if (this.writeAcceptCharset) { outputMessage.getHeaders().setAcceptCharset(getAcceptedCharsets()); } Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType()); StreamUtils.copy(str, charset, outputMessage.getBody()); }
注意:乱码问题就是在这边产生的
Charset charset = getContentTypeCharset(outputMessage.getHeaders().getContentType());
首先会看你的请求是否有设置charset,charset的设置在自己编写restTemplate请求代码时做的如下代码就指定了编码格式为UTF-8
HttpHeaders headers = new HttpHeaders(); MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8"); headers.setContentType(type); headers.add("Accept", MediaType.APPLICATION_JSON.toString()); HttpEntity request = new HttpEntity<>(jsonObject, headers); JSONObject response = restTemplate.postForObject("http://127.0.0.1:8089/getBodyTest", request, JSONObject.class);
如果MediaType没有指定charset,而是这样的
MediaType type = MediaType.parseMediaType("application/json");
那么将使用converter的默认编码,还是以StringHttpMessageConverter为例
private Charset getContentTypeCharset(MediaType contentType) { if (contentType != null && contentType.getCharset() != null) { return contentType.getCharset(); } else { return getDefaultCharset(); } }
没有设置就会调用getDefaultCharset()
public Charset getDefaultCharset() { return this.defaultCharset; }
this.defaultCharset是这样的
private Charset defaultCharset;
这边没有给默认值,说明在其他地方赋值的,那我们看下StringHttpMessageConverter的构造方法
public StringHttpMessageConverter() { this(DEFAULT_CHARSET); } public StringHttpMessageConverter(Charset defaultCharset) { super(defaultCharset, MediaType.TEXT_PLAIN, MediaType.ALL); }
而DEFAULT_CHARSET的值如下
public static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");
所以说,如果我们在通过restTemplate发起http请求,如果body是String格式的,那么他的默认编码是"ISO-8859-1",那么就会出现乱码的可能,解决的方法也很简单,就是自己设置下charset
(三)response = request.execute();
这边以SimpleBufferingClientHttpRequest举例
执行的核心代码是这个
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException { addHeaders(this.connection, headers); // JDK <1.8 doesn't support getOutputStream with HTTP DELETE if (getMethod() == HttpMethod.DELETE && 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 { // Immediately trigger the request in a no-output scenario as well this.connection.getResponseCode(); } return new SimpleClientHttpResponse(this.connection); }
主要是进行了this.connection.connect();然后将connection包装在了SimpleClientHttpResponse中并返回了
(四)handleResponse(url, method, response);
这一步就是对http的response做处理,这里有个知识点,就是开头提到的直接通过log.error("http response error,{}",e)打印出来的日志不是我们想要的完整日志
protected void handleResponse(URI url, HttpMethod method, ClientHttpResponse response) throws IOException { ResponseErrorHandler errorHandler = getErrorHandler(); boolean hasError = errorHandler.hasError(response); if (logger.isDebugEnabled()) { try { logger.debug(method.name() + " request for \"" + url + "\" resulted in " + response.getRawStatusCode() + " (" + response.getStatusText() + ")" + (hasError ? "; invoking error handler" : "")); } catch (IOException ex) { // ignore } } if (hasError) { errorHandler.handleError(response); } }
getErrorHandler是这样子的,
public ResponseErrorHandler getErrorHandler() { return this.errorHandler; }
而this.errorHandler是这样子的
private ResponseErrorHandler errorHandler = new DefaultResponseErrorHandler();
所以,如果我们没做任何设置,那么这里的ResponseErrorHandler就是DefaultResponseErrorHandler
对于DefaultResponseErrorHandler,他对hasError(response)的实现就是判断是否是400等4开头或者500等5开头的返回状态吗,如果是则把返回包装下,抛出一条异常
public void handleError(ClientHttpResponse response) throws IOException { HttpStatus statusCode = getHttpStatusCode(response); switch (statusCode.series()) { case CLIENT_ERROR: throw new HttpClientErrorException(statusCode, response.getStatusText(), response.getHeaders(), getResponseBody(response), getCharset(response)); case SERVER_ERROR: throw new HttpServerErrorException(statusCode, response.getStatusText(), response.getHeaders(), getResponseBody(response), getCharset(response)); default: throw new UnknownHttpStatusCodeException(statusCode.value(), response.getStatusText(), response.getHeaders(), getResponseBody(response), getCharset(response)); } }
由于doExecute已经包了一层tray/catch,所以等我们拿到时就是这样的异常了
String resource = url.toString(); String query = url.getRawQuery(); resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource); throw new ResourceAccessException("I/O error on " + method.name() + " request for \"" + resource + "\": " + ex.getMessage(), ex);
所以我们的打印日志中无法显示返回的body。
如果想打印出body,这个有个简单的方式,就是自己实现ResponseErrorHandler,如下
public class RestTemplateResponseErrorHandler implements ResponseErrorHandler { @Override public boolean hasError(ClientHttpResponse response) throws IOException { return true; } @Override public void handleError(ClientHttpResponse response) throws IOException { } }
然后在resttemplate初始化时将这个handler设置下就行了
@Bean public RestTemplate restTemplate(ClientHttpRequestFactory factory) { RestTemplate restTemplate = new RestTemplate(factory); restTemplate.setErrorHandler(new RestTemplateResponseErrorHandler()); return restTemplate; }
这样子,返回的原始信息我们都可以拿到了。
(五)responseExtractor.extractData(response);
这一步就是通过converter将response中的body转换成我们设置的类,大致代码和wirte差不多,只是这边是read
public T extractData(ClientHttpResponse response) throws IOException { MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response); if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) { return null; } MediaType contentType = getContentType(responseWrapper); for (HttpMessageConverter<?> messageConverter : this.messageConverters) { if (messageConverter instanceof GenericHttpMessageConverter) { GenericHttpMessageConverter<?> genericMessageConverter = (GenericHttpMessageConverter<?>) messageConverter; if (genericMessageConverter.canRead(this.responseType, null, contentType)) { if (logger.isDebugEnabled()) { logger.debug("Reading [" + this.responseType + "] as \"" + contentType + "\" using [" + messageConverter + "]"); } return (T) genericMessageConverter.read(this.responseType, null, responseWrapper); } } if (this.responseClass != null) { if (messageConverter.canRead(this.responseClass, contentType)) { if (logger.isDebugEnabled()) { logger.debug("Reading [" + this.responseClass.getName() + "] as \"" + contentType + "\" using [" + messageConverter + "]"); } return (T) messageConverter.read((Class) this.responseClass, responseWrapper); } } } throw new RestClientException("Could not extract response: no suitable HttpMessageConverter found " + "for response type [" + this.responseType + "] and content type [" + contentType + "]"); }