一、背景
今天有个同事使用RestTemplate想设置超时时间,不知道怎么设置,帮忙翻了下源码,萌生了写个源码学习的文章
二、简述RestTemplate
RestTemplate是Spring框架中的一个核心类,用于在客户端(例如Web应用程序)中调用RESTful服务。它是一个HTTP客户端,可以用于向RESTful服务发送HTTP请求,并接收响应。
RestTemplate可以发送HTTP GET、POST、PUT、DELETE等请求,支持JSON、XML等数据格式的请求和响应。
RestTemplate默认使用Java的URLConnection来发送HTTP请求,也可以配置成使用Apache HttpClient、OkHttp等第三方HTTP客户端。可以通过配置RestTemplate的ClientHttpRequestFactory来指定使用的HTTP客户端。
三、用HttpURLConnection写个http连接请求
我们这先用jdk自带包里的HttpURLConnection写个http请求,代码如下:
@Test
public void testHttpGet() {
//链接
HttpURLConnection connection = null;
InputStream is = null;
BufferedReader br = null;
StringBuffer result = new StringBuffer();
try {
//创建连接
URL url = new URL("https://www.baidu.com");
connection = (HttpURLConnection) url.openConnection();
//设置请求方式
connection.setRequestMethod("GET");
//设置连接超时时间
connection.setReadTimeout(15000);
//开始连接
connection.connect();
//获取响应数据
if (connection.getResponseCode() == 200) {
//获取返回的数据
is = connection.getInputStream();
if (null != is) {
br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
String temp;
while (null != (temp = br.readLine())) {
result.append(temp);
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != br) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != is) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//关闭远程连接
connection.disconnect();
}
System.out.println(result.toString());
}
有几个比较关键的代码,我们可以先记住,之后的源码跟读中,可以找找在源码中对应的位置
- url.openConnection()
- connection.connect();
- connection.getResponseCode()
- connection.getInputStream()
四、RestTemplate源码
1、RestTemplate的类图
首先看下RestTemplate的类图
RestOperations是一个接口,抽象出了Restful风格的操作方法
HttpAccessor是一个抽象类,内部定义创建连接的工厂类,默认是SimpleClientHttpRequestFactory,也可以自己设置,另外就是创建Request对象的方法createRequest,具体交给工厂类去创建
InterceptingHttpAccessor是HttpAccessor的抽象子类,在HttpAccessor的基础上,增加了拦截器的逻辑
2、写个例子
@Test
public void testRestTemplateGet() {
RestTemplate INSTANCE = new RestTemplate();
String result = INSTANCE.getForObject("https://www.baidu.com", String.class);
System.out.println(result);
}
3、构造方法
public RestTemplate() {
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
this.messageConverters.add(new ResourceHttpMessageConverter(false));
try {
this.messageConverters.add(new SourceHttpMessageConverter<>());
}
catch (Error err) {
// Ignore when no TransformerFactory implementation is available
}
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());
}
this.uriTemplateHandler = initUriTemplateHandler();
}
构造方法中主要干了两件事:
- 添加了很多HttpMessageConverter的实例对象,针对不同的消息形式,有不同的HttpMessageConverter实现类来处理
- initUriTemplateHandler初始化URI模板处理器,用来拼接url的
4、执行请求
4.1、getForObject
//RestTemplate
@Override
@Nullable
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
//获取请求回调
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
//创建http消息转换抽取器
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}
这个方法包含:
- acceptHeaderRequestCallback获取请求回调:该方法内部创建了AcceptHeaderRequestCallback类的实例,AcceptHeaderRequestCallback是RestTemplate的内部类,里面只有一个实现方法,doWithRequest,这个方法就是遍历所有的消息转换器,找到适合的,然后再从这些转换器中找到所有支持的媒体类型,最后将所有支持的媒体类型设置到请求头部Accept中。
- HttpMessageConverterExtractor创建http消息转换抽取器:该方法就是将消息转换器,log,返回类型封装成HttpMessageConverterExtractor对象,供后面使用
- execute执行请求方法:后面细说
4.2、execute
//RestTemplate
@Override
@Nullable
public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException {
//拼接url
URI expanded = getUriTemplateHandler().expand(url, uriVariables);
return doExecute(expanded, method, requestCallback, responseExtractor);
}
这步没什么好说的,拼接url之后,调用doExecute,我们看doExecute(一般do开头的是真正的核心代码)
4.3、doExecute
//RestTemplate
@Nullable
protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor<T> responseExtractor) throws RestClientException {
Assert.notNull(url, "URI is required");
Assert.notNull(method, "HttpMethod is required");
ClientHttpResponse response = null;
try {
//创建ClientHttpRequest
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
//处理回调
requestCallback.doWithRequest(request);
}
//执行请求
response = request.execute();
//处理响应
handleResponse(url, method, response);
//用响应抽取器抽取数据,并转换成我们想要的返回类型
return (responseExtractor != null ? responseExtractor.extractData(response) : 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();
}
}
}
这一步有几个重要的方法:
- createRequest(url, method);
- requestCallback.doWithRequest(request);
- response = request.execute();
- handleResponse(url, method, response);
- responseExtractor.extractData(response)
下面分别看看这几个方法
4.4、createRequest(url, method);
//HttpAccessor
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
ClientHttpRequest request = getRequestFactory().createRequest(url, method);
if (logger.isDebugEnabled()) {
logger.debug("HTTP " + method.name() + " " + url);
}
return request;
}
这里使用的是工厂方法,先获取工厂类,然后用工厂类创建ClientHttpRequest对象,先看下getRequestFactory方法,因为这个方法被子类实现了,所以调用的是子类InterceptingHttpAccessor里的getRequestFactory方法
//InterceptingHttpAccessor
@Override
public ClientHttpRequestFactory getRequestFactory() {
List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
if (!CollectionUtils.isEmpty(interceptors)) {
ClientHttpRequestFactory factory = this.interceptingRequestFactory;
if (factory == null) {
factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
this.interceptingRequestFactory = factory;
}
return factory;
}
else {
return super.getRequestFactory();
}
}
这里是获取拦截器,如果有拦截器就封装成InterceptingClientHttpRequestFactory这个包含拦截器的工厂类,最终会创建包含拦截器的InterceptingClientHttpRequest,如果没有拦截器,就调用父类的getRequestFactory方法,返回HttpAccessor里默认的工厂类,这里我们没有加拦截器,我们看HttpAccessor里默认的工厂类
private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
HttpAccessor默认的工厂类是SimpleClientHttpRequestFactory,我们再回到createRequest方法
//HttpAccessor
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
ClientHttpRequest request = getRequestFactory().createRequest(url, method);
if (logger.isDebugEnabled()) {
logger.debug("HTTP " + method.name() + " " + url);
}
return request;
}
我们再来看工厂的createRequest方法,上面我们知道返回的工厂类是SimpleClientHttpRequestFactory,所以下一步的它的createRequest方法
//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);
}
}
这一步主要有两个方法,最后返回一个SimpleBufferingClientHttpRequest对象的Request
- openConnection
- prepareConnection
openConnection,看名字就知道是打开连接,里面可以找到打开连接的方法url.openConnection()
//SimpleClientHttpRequestFactory
protected HttpURLConnection openConnection(URL url, @Nullable Proxy proxy) throws IOException {
URLConnection urlConnection = (proxy != null ? url.openConnection(proxy) : url.openConnection());
if (!HttpURLConnection.class.isInstance(urlConnection)) {
throw new IllegalStateException("HttpURLConnection required for [" + url + "] but got: " + urlConnection);
}
return (HttpURLConnection) urlConnection;
}
继续看prepareConnection,这步是设置connection的一些属性
//SimpleClientHttpRequestFactory
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(true);
}
else {
connection.setDoOutput(false);
}
//设置请求方法
connection.setRequestMethod(httpMethod);
}
4.5、requestCallback.doWithRequest(request);
回到doExecute,我们再来看requestCallback.doWithRequest(request);前面我们看到RequestCallback的实例是AcceptHeaderRequestCallback,我们看下它的doWithRequest方法
//AcceptHeaderRequestCallback
@Override
public void doWithRequest(ClientHttpRequest request) throws IOException {
if (this.responseType != null) {
List<MediaType> allSupportedMediaTypes = getMessageConverters().stream()
.filter(converter -> canReadResponse(this.responseType, converter))
.flatMap(this::getSupportedMediaTypes)
.distinct()
.sorted(MediaType.SPECIFICITY_COMPARATOR)
.collect(Collectors.toList());
if (logger.isDebugEnabled()) {
logger.debug("Accept=" + allSupportedMediaTypes);
}
request.getHeaders().setAccept(allSupportedMediaTypes);
}
}
这一步就是把支持的MediaType设置进请求头中
4.6、response = request.execute();
回到doExecute,继续看request.execute(),前面我们知道这个request是SimpleBufferingClientHttpRequest类型的,但是点击request.execute方法却找不到这个类型
应该是在其父类中,所以我们先看类图
所以在其父类AbstractClientHttpRequest中
//AbstractClientHttpRequest
@Override
public final ClientHttpResponse execute() throws IOException {
//检查是否执行过,如果执行过会报错
assertNotExecuted();
//执行请求
ClientHttpResponse result = executeInternal(this.headers);
//执行后标记为已执行
this.executed = true;
return result;
}
重点看executeInternal,在子类AbstractBufferingClientHttpRequest中
//AbstractBufferingClientHttpRequest
@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
byte[] bytes = this.bufferedOutput.toByteArray();
if (headers.getContentLength() < 0) {
headers.setContentLength(bytes.length);
}
ClientHttpResponse result = executeInternal(headers, bytes);
this.bufferedOutput = new ByteArrayOutputStream(0);
return result;
}
再看executeInternal(headers, bytes);在子类SimpleBufferingClientHttpRequest中
//SimpleBufferingClientHttpRequest
@Override
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);
}
connection.connect(); 找到了,最后将connection封装到SimpleClientHttpResponse类型的Response里
4.7、handleResponse(url, method, response);
再回到doExecute,继续看handleResponse(url, method, response);
//RestTemplate
protected void handleResponse(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
//获取错误处理器
ResponseErrorHandler errorHandler = getErrorHandler();
//判断返回有没有错误
boolean hasError = errorHandler.hasError(response);
if (logger.isDebugEnabled()) {
try {
//获取状态码
int code = response.getRawStatusCode();
//将状态码转换成HttpStatus枚举类
HttpStatus status = HttpStatus.resolve(code);
logger.debug("Response " + (status != null ? status : code));
}
catch (IOException ex) {
// ignore
}
}
if (hasError) {
//有错误,用错误处理器处理
errorHandler.handleError(url, method, response);
}
}
这一步主要就是判断返回有没有错误,有错误就处理错误,处理错误最终就是抛异常,所以最后那步的抽取数据不会执行,所以重点就是errorHandler.hasError(response);跟进去
//DefaultResponseErrorHandler
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
int rawStatusCode = response.getRawStatusCode();
HttpStatus statusCode = HttpStatus.resolve(rawStatusCode);
return (statusCode != null ? hasError(statusCode) : hasError(rawStatusCode));
}
response.getRawStatusCode();跟进去,到类SimpleClientHttpResponse中
//SimpleClientHttpResponse
@Override
public int getRawStatusCode() throws IOException {
return this.connection.getResponseCode();
}
找到connection.getResponseCode(); 了
再回头看下hasError(statusCode),一直跟进去
//HttpStatus
public boolean isError() {
return (is4xxClientError() || is5xxServerError());
}
其实就是看如果是4或者5开头的,就是错误
4.8、responseExtractor.extractData(response)
最后来看responseExtractor.extractData(response),我们前面知道responseExtractor是HttpMessageConverterExtractor类型的
//HttpMessageConverterExtractor
@Override
@SuppressWarnings({"unchecked", "rawtypes", "resource"})
public T extractData(ClientHttpResponse response) throws IOException {
MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
return null;
}
MediaType contentType = getContentType(responseWrapper);
try {
//遍历所有的HttpMessageConverter
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
//如果是GenericHttpMessageConverter类型的,走这个分支
if (messageConverter instanceof GenericHttpMessageConverter) {
GenericHttpMessageConverter<?> genericMessageConverter =
(GenericHttpMessageConverter<?>) messageConverter;
if (genericMessageConverter.canRead(this.responseType, null, contentType)) {
if (logger.isDebugEnabled()) {
ResolvableType resolvableType = ResolvableType.forType(this.responseType);
logger.debug("Reading to [" + resolvableType + "]");
}
return (T) genericMessageConverter.read(this.responseType, null, responseWrapper);
}
}
//非GenericHttpMessageConverter类型的,走这个分支
if (this.responseClass != null) {
//根据返回的类型判断这个messageConverter是否可以读取
if (messageConverter.canRead(this.responseClass, contentType)) {
if (logger.isDebugEnabled()) {
String className = this.responseClass.getName();
logger.debug("Reading to [" + className + "] as \"" + contentType + "\"");
}
return (T) messageConverter.read((Class) this.responseClass, responseWrapper);
}
}
}
}
catch (IOException | HttpMessageNotReadableException ex) {
throw new RestClientException("Error while extracting response for type [" +
this.responseType + "] and content type [" + contentType + "]", ex);
}
throw new RestClientException("Could not extract response: no suitable HttpMessageConverter found " +
"for response type [" + this.responseType + "] and content type [" + contentType + "]");
}
这一步简单来说就是,遍历所有的HttpMessageConverter,如果发现哪个可以读取响应数据,就用这个HttpMessageConverter转换后返回,由于我们设置的返回类型是String,所以这里是用的StringHttpMessageConverter,先进入其父类AbstractHttpMessageConverter的read方法
//AbstractHttpMessageConverter
@Override
public final T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
return readInternal(clazz, inputMessage);
}
再进入子类StringHttpMessageConverter的readInternal方法
//StringHttpMessageConverter
@Override
protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException {
Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType());
return StreamUtils.copyToString(inputMessage.getBody(), charset);
}
先获取字符集,这个没什么好说的,我们重点看下inputMessage.getBody(),想找到最后的那个connection.getInputStream(),我们前面知道inputMessage是MessageBodyClientHttpResponseWrapper类型的
//MessageBodyClientHttpResponseWrapper
@Override
public InputStream getBody() throws IOException {
return (this.pushbackInputStream != null ? this.pushbackInputStream : this.response.getBody());
}
可以看到来源是pushbackInputStream,pushbackInputStream是在调用hasEmptyMessageBody方法时设置的,其实就是response.getBody()的返回做了一层包装,我们看下response.getBody(),在SimpleClientHttpResponse类中
//SimpleClientHttpResponse
@Override
public InputStream getBody() throws IOException {
InputStream errorStream = this.connection.getErrorStream();
this.responseStream = (errorStream != null ? errorStream : this.connection.getInputStream());
return this.responseStream;
}
终于找到了connection.getInputStream()
再回头看StreamUtils.copyToString方法
public static String copyToString(@Nullable InputStream in, Charset charset) throws IOException {
if (in == null) {
return "";
}
StringBuilder out = new StringBuilder();
InputStreamReader reader = new InputStreamReader(in, charset);
char[] buffer = new char[BUFFER_SIZE];
int bytesRead = -1;
while ((bytesRead = reader.read(buffer)) != -1) {
out.append(buffer, 0, bytesRead);
}
return out.toString();
}
是不是和之前写的例子很像,读取流,拼接成结果返回;
到此RestTemplate源码分析结束;
五、什么是Restful风格
看完源码,我们知道,其实RestTemplate封装了原生的HttpURLConnection,采用Restful的理念,更优雅地来完成对HTTP服务的调用,那么什么是Restful风格,网上看到大神的回答
用 URL 定位资源,用 HTTP 动词(GET,POST,DELETE,PUT)描述操作。
用URL来指定具体的资源,如图片,文字,或者一个服务;用HTTP动词来描述操作;
再回头看RestTemplate,它用HttpMethod来定义Http的动词,包含下面这些:
- GET:请求服务器端已经存在的资源,通常将Query String查询条件以键值对的编码形式追加到URL中。
- HEAD:HEAD与GET几乎相同,但没有响应主体,只返回响应头部信息。HEAD请求对于在实际发出GET请求之前(例如在下载大文件或响应正文之前)检查GET请求将返回的内容很有用。
- POST:POST用于用户创建,获取,或更新服务器上的资源,通过POST发送到服务器的数据存储在HTTP请求的请求主体中。
- PUT:PUT用于将数据发送到服务器来创建/更新资源。POST和PUT之间的区别在于PUT请求是幂等的。也就是说,多次调用相同的PUT请求将始终产生相同的结果。相反,重复调用POST请求具有多次创建相同资源的副作用。
- PATCH:用来更新服务器端已经存在资源的局部属性。
- DELETE:用于删除服务器端指定的资源,指定资源的设定追加在URL中。
- OPTIONS:描述目标资源所支持的http方法选项。
- TRACE:诊断请求,允许客户端查看最终发送到服务器的请求消息(由于请求在中间节点可能会被修改)。提供了一种实用的debug机制。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/154486.html