Spring RestTemplate の使用法とソースコード分析
1.RestTemplateの概要
RestTemplate は、Spring Web モジュールによってカプセル化された Rest 仕様に基づいて HTTP リクエスト サービスを提供するツールであり、サードパーティの Rest インターフェイスにアクセスするために使用されます。従来、サーバーが
HTTP
サービスにアクセスするときは、通常、JDK
またはを使用しますがHttpURLConnection
、これらの方法とツールは扱いにくく、非常に複雑で、手動でリソースを回復する必要がありますApache
。このコンテキストでは、RestTemplate は HTTP サービスとの通信を簡素化し、RestFul 原則を満たしているため、リクエストの送信と結果の取得だけに集中する必要があります。これは、リクエストを実行するための同期ブロッキング ツール クラスです。リクエストの構築、リソースのリサイクル、クライアント ライブラリ ( HTTP サービス ソースなど)に基づいたエラー処理などの基本的な操作のみをカプセル化し、よりシンプルで簡単なサービスを提供します。 -to-useテンプレートメソッドAPIにより開発効率が大幅に向上しました。HttpClient
API
RestTemplate
HTTP
HTTP
JDK HttpURLConnection
Apache HttpComponents
okHttp
Spring 5.0 以降、RestTemplate はメンテナンス モードになっています。これは、より最新の API を提供し、同期、非同期、およびストリーミング ソリューションをサポートする WebClient に置き換えられ、より複雑で豊富な、より柔軟なアプリケーション シナリオをサポートします。ここではそれについては話しません。
参照文書:
- 概要 (Spring Framework 5.3.22 API)
- サーブレット スタック上の Web (spring.io)
- Spring-RestTemplate の実行原理の分析
- RestTemplate を使用してマイクロサービスで API (インターセプター、例外処理、メッセージ変換) をエレガントに呼び出す方法 - ccww_ のパーソナル スペース - OSCHINA - 中国のオープンソース テクノロジー交換コミュニティ
2. RestTemplate の使用
1.RestTemplateの紹介
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.RestTemplate 配置
2.1 HTTPソースの構成
(1) HTTPクライアントの導入
RestTemplate は HttpAccessor を継承します。このクラスは、HTTP の基礎となるクライアントに接続/アクセスするための抽象基本クラスとして理解できます。そのソース コードは次のとおりです。このクラスの ClientHttpRequestFactory ファクトリ (またはクライアント リクエスト ライブラリ) 属性は、対応する基礎となる HTTP 接続クライアントを介してリクエスト リクエストを構築し、上向きの HTTP リクエスト アクセス サービスを提供するために特別に使用され、そのデフォルトの割り当ては SimpleClientHttpRequestFactory であることがわかります。異なる HTTP ソース間で切り替えるには、setRequestFactory メソッドを通じて他のクライアントを ClientHttpRequestFactory に割り当てる必要があります。
public abstract class HttpAccessor {
protected final Log logger = HttpLogging.forLogName(this.getClass());
private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
private final List<ClientHttpRequestInitializer> clientHttpRequestInitializers = new ArrayList();
public HttpAccessor() {
}
public void setRequestFactory(ClientHttpRequestFactory requestFactory) {
Assert.notNull(requestFactory, "ClientHttpRequestFactory must not be null");
this.requestFactory = requestFactory;
}
... ...
}
基盤となる HTTP クライアントは HTTP 接続を確立するために使用され、ファクトリは対応する HTTP 接続からリクエストを構築してリクエストを開始するために使用され、RestTemplate は上記のプロセスをカプセル化し、よりシンプルで便利なテンプレート API を提供するために使用されます。外の世界へ。RestTemplate をサポートするHTTP クライアント接続ライブラリは、ClientHttpRequestFactory インターフェイスから実装されます。一般的なクライアント ライブラリとそれに対応する HTTP クライアントは次のように紹介されます。
HTTP
SimpleClientHttpRequestFactory: RestTemplate のデフォルトのクライアント ライブラリ、およびそれに対応する HTTP 接続クライアント タイプは、基盤となるクライアント実装として Java JDK に付属する HttpURLConnection です。- HttpComponentsAsyncClientHttpRequestFactory: 対応する HTTP 接続クライアント タイプは、基礎となる
HTTP
クライアント実装としての Apache の HttpClient です。- OkHttp3ClientHttpRequestFactory: 対応する HTTP 接続クライアント タイプは、基礎となる
HTTP
クライアント実装として OkHttpClient です。開発者からのフィードバックや、
HTTP
インターネット上のさまざまなクライアントのパフォーマンスや使いやすさの評価から判断すると、優れていOkHttpClient
ます。Java JDK に付属の HttpURLConnection は HTTP プロトコルの Patch メソッドをサポートしていないことに注意してください。setRequestFactory メソッドを設定することで、RestTemplate の基になる HTTP クライアント実装クラス ライブラリを切り替えることができます。Apache的HttpClient
Apache的HttpClient
HttpURLConnection
(2) HTTPクライアントの切り替え
- デフォルトの SimpleClientHttpRequestFactory 構成
@Configuration
public class RestTemplateConfig {
@ConditionalOnMissingBean(RestTemplate.class)
@Bean
public RestTemplate restTemplate() {
return new RestTemplate(getClientHttpRequestFactory());
}
private ClientHttpRequestFactory getClientHttpRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(150000); //设置传输/读取数据的超时时间(以毫秒为单位)
factory.setConnectTimeout(150000); //设置Connection的连接超时时间(以毫秒为单位)
//factory.setBufferRequestBody(true); //设置是否应在factory内部缓冲/缓存请求正文body数据(传输大数据时应设置为true,默认为true)
return factory;
}
}
- HttpComponentsAsyncClientHttpRequestファクトリー構成
HttpComponentsAsyncClientHttpRequestFactory は、Apache HttpComponents の HttpClient クライアントを使用して Request リクエストを作成し、認証や HTTP 接続プーリングなどの機能を拡張します。構成手順には主に次のものが含まれます。
- HttpClient 依存関係パッケージを導入します (そうしないと、接続プール、RequestConfig などを構成できず、デフォルト構成のみを使用できます)
- Http接続プール設定(接続モード、最大接続数など)、リクエストリクエスト設定(接続時間、読み込み時間など)、HttpClientクライアント設定
- HttpComponentsAsyncClientHttpRequestファクトリー構成
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
@Configuration
public class RestTemplateConfig {
@Bean
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
return restTemplate;
}
private ClientHttpRequestFactory getClientHttpRequestFactory(){
/**
factory.setHttpClient(); //设置HttpClient客户端
factory.setConnectionRequestTimeout(); //等价于设置其RequestConfig的ConnectionRequestTimeout
factory.setConnectTimeout(); //等价于设置其RequestConfig的ConnectTimeout
factory.setReadTimeout(); //等价于设置其RequestConfig的SocketTimeout
**/
return new HttpComponentsClientHttpRequestFactory(getHttpClient());
}
private HttpClient getHttpClient(){
//1.注册HTTP和HTTPS请求服务(ConnectionManager初始化默认值已经注册完毕)
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory.getSocketFactory())
.build();
//2.声明连接池配置
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
connectionManager.setMaxTotal(1000);//设置连接池最大连接数
connectionManager.setDefaultMaxPerRoute(500); // 单个连接路由(单个主机)的最大并发连接数
connectionManager.setValidateAfterInactivity(3000); //最大连接空闲时间,重用空闲连接时会先检查是否空闲时间超过这个时间,如果超过则释放socket重新建立
//3.请求配置RequestConfig
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(2000) //从连接池中获取连接的最大超时时间,超时未拿到可用连接则会抛出异常
.setConnectTimeout(1000) //建立连接(握手成功)的最大超时时间
.setSocketTimeout(1000) //等待服务器返回响应(response)的最大超时时间
.build();
//4.设置默认请求头 headers
List<Header> headers = new ArrayList<>();
headers.add(new BasicHeader("Accept-Encoding", "gzip,deflate"));
headers.add(new BasicHeader("Accept-Language", "zh-CN"));
headers.add(new BasicHeader("Connection", "Keep-Alive"));
headers.add(new BasicHeader("Content-type", "application/json;charset=UTF-8"));
//5.配置HttpClient客户端
return HttpClientBuilder.create()
.setDefaultRequestConfig(requestConfig) //引入RequestConfig请求配置
.setConnectionManager(connectionManager) //引入PoolingHttpClientConnectionManager连接池配置
.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy()) //保持长连接配置,需要在头添加 Keep-Alive(默认策略,返回此连接可以安全保持空闲状态的持续时间。如果连接处于空闲状态的时间超过此时间段,则不得重复使用。)
.setDefaultHeaders(headers) //设置默认请求头
.setRetryHandler(new DefaultHttpRequestRetryHandler(3,true)) //设置异常重试次数、是否开启重试(默认为3次,false不开启)
.build();
}
}
- OkHttp3ClientHttpRequestファクトリー構成
OkHttp は効率的な HTTP クライアントです。
- 同じホスト アドレスからのすべてのリクエストが同じソケット接続を共有できるようにします。
- 接続プールはリクエストの遅延を減らすことができます。
- 透過的な GZIP 圧縮により、応答データのサイズが削減されます。
- 応答をキャッシュすると、一部の完全に重複したネットワーク要求を完全に回避できます。
ネットワークに問題がある場合でも、OkHttp は責任を果たし、一般的な接続の問題を自動的に復元します。サービスに複数の IP アドレスがある場合、最初の IP リクエストが失敗すると、OkHttp は構成した他の構成を交互に試みます。 IP。OkHttp の使用は簡単で、そのリクエスト/レスポンス API には流暢なビルダーと不変性があります。同期ブロッキング呼び出しとコールバックを伴う非同期呼び出しをサポートします。
<!--引入okhttp依赖-->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
</dependency>
@Configuration
public class RestTemplateConfig {
@Bean
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
return restTemplate;
}
/**
* 使用OkHttpClient作为底层客户端
* @return
*/
private ClientHttpRequestFactory getClientHttpRequestFactory(){
OkHttpClient okHttpClient = new OkHttpClient.newBuilder()
.connectionPool(pool())
.connectTimeout(5, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.build();
return new OkHttp3ClientHttpRequestFactory(okHttpClient);
}
/**
* 连接池配置 ConnectionPool
* Manages reuse of HTTP and HTTP/2 connections for reduced network latency. HTTP requests that share the same [Address] may share a [Connection].
* @return
*/
private ConnectionPool pool() {
//maxIdleConnections 最大空闲连接数(默认5 min)
//keepAliveDuration 空闲连接存活时间(默认5 min)
return new ConnectionPool(200, 10, TimeUnit.SECONDS);
}
}
2.2 インターセプターの構成
場合によっては、リクエスト ログの印刷、トークン検証の追加、デフォルトのヘッダー情報など、リクエストの一般的なインターセプト設定を行う必要がある場合、RestTemplate リクエストを処理するインターセプターを追加できます。RestTemplate は、setInterceptors メソッドを介して内部インターセプター チェーン リストを設定し、リクエストをインターセプトして処理します。各カスタム インターセプターは ClientHttpRequestInterceptor インターフェイスを実装する必要があります。
/**
* 记录RestTemplate访问信息日志
*/
public class TrackLogClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
/**
* intercept : 拦截 RestTemplate 请求处理,并返回响应Response
* @param request the request, containing method, URI, and headers
* @param body the body of the request
* @param execution the request execution 请求上下文
* - 若当前拦截器非链上最后一个拦截器:用于调用拦截器链chain中的下一个拦截器,将请求信息向链后传递
* - 若当前拦截器为最后一个拦截器:用于执行request请求本身,并将响应向链前返回
*/
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
trackRequest(request,body);
//调用execution传递request
ClientHttpResponse httpResponse = execution.execute(request, body);
trackResponse(httpResponse);
return httpResponse;
}
//记录响应responset日志
private void trackResponse(ClientHttpResponse httpResponse)throws IOException {
System.out.println("============================response begin==========================================");
System.out.println("Status code : "+httpResponse.getStatusCode());
System.out.println("Status text : "+httpResponse.getStatusText());
System.out.println("Headers : "+httpResponse.getHeaders());
System.out.println("=======================response end=================================================");
}
//记录请求request日志
private void trackRequest(HttpRequest request, byte[] body)throws UnsupportedEncodingException {
System.out.println("======= request begin ========");
System.out.println("uri : "+request.getURI());
System.out.println("method : "+request.getMethod());
System.out.println("headers : "+request.getHeaders());
System.out.println("request body : "+new String(body, "UTF-8"));
System.out.println("======= request end ========");
}
}
/**
* 请求头部添加Token信息
*/
public class TokenClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
//生成令牌 此处调用一个自己写的方法,有兴趣的朋友可以自行google如何使用ak/sk生成token,此方法跟本教程无关,就不贴出来了
String token = TokenHelper.generateToken(checkTokenUrl, ttTime, methodName, requestBody);
//将令牌放入请求header中
request.getHeaders().add("X-Auth-Token",token);
return execution.execute(request,body);
}
}
@Configuration
public class RestTemplateConfig {
@Bean
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
//配置自定义拦截器
List<ClientHttpRequestInterceptor> interceptors=new ArrayList<ClientHttpRequestInterceptor>();
interceptors.add(new TokenClientHttpRequestInterceptor());
interceptors.add(new TrackLogClientHttpRequestInterceptor());
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
}
2.3 コンバータの設定
Reseful インターフェースによって渡されるデータ内容は json 形式の文字列ですが、
RestTemplate
カプセル化方法では Java クラスをデータ オブジェクトとして直接渡し、それがHttpMessageConverter
最下層で自動的に変換されます。POST リクエストが送信される前または応答が受信された後、RestTemplate は内部の messageConverters リストを走査し、設定された戻り値の型の responseType とリクエストの contentType パラメーターを照合し、最初の適切な MessageConverter を選択してリクエストの本文データと照合して処理します。
(1) コンバータはデータ関係に対応します
デフォルトでは、初期化中にいくつかの異なるタイプのリクエストを処理できるよう
RestTemplate
に、一連の自己完結型リクエストが自動的に登録されます。異なる Convert と MediaType 間の対応関係は次のとおりです。HttpMessageConverter
contentType
クラス名 | サポートされている JavaType | サポートされているメディアタイプ |
---|---|---|
ByteArrayHttpMessageConverter | バイト[] | すべてのメディア タイプ ( */* ) をサポートし、(バイト配列 http メッセージ コンバーター)Content-Type で書き込みますapplication/octet-stream |
StringHttpMessageConverter | 弦 | すべてのテキスト メディア タイプ ( text/* ) をサポートし、 . (文字列 http メッセージ コンバータ) で書き込みContent-Type ますtext/plain 。 |
ResourceHttpMessageConverter | リソース | / (メディアやファイルの読み取りなど、リソースの読み取りと書き込みのための http メッセージ コンバーター) |
SourceHttpMessageConverter | ソース | application/xml、text/xml、application/*+xml (ソース http メッセージ コンバーターはソース タイプ オブジェクトの変換に使用されます) |
AllEncompassingFormHttpMessageConverter | マップ<K、リスト<?>> | application/x-www-form-urlencoded、multipart/form-data (すべて汎用メッセージ コンバーター) |
MappingJackson2HttpMessageConverter | 物体 | application/json、application/*+json (ジャクソン メッセージ コンバーターは json および Java オブジェクトを変換できます) |
Jaxb2RootElementHttpMessageConverter | 物体 | application/xml、text/xml、application/*+xml (JAXB は Java オブジェクトと XML を変換できます) |
JavaSerializationConverter | シリアル化可能 | x-java-serialization;charset=UTF-8 (シリアル化コンバーター) |
FastJsonHttpMessageConverter | 物体 | / (FastJson メッセージ コンバーターは、json オブジェクトと Java オブジェクトの間で変換できます) |
(2) FastJsonHttpMessageConvertコンバータの登録
<!-- 引入alibaba fastjson 依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
@Configuration
public class RestTemplateConfig {
@Bean
@ConditionalOnMissingBean(RestTemplate.class)
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate(getClientHttpRequestFactory());
//1.获取restTemplate的MessageConverters List
List<HttpMessageConverter<?>> messageConverters= restTemplate.getMessageConverters();
//2.手动创建并配置FastJsonConverter
FastJsonHttpMessageConverter fastJsonConverter = new FastJsonHttpMessageConverter();
// 添加fastJson的配置信息;
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(
SerializerFeature.WriteMapNullValue, // 是否输出值为null的字段,默认为false,我们将它打开
SerializerFeature.WriteNullListAsEmpty, // 将Collection类型字段的字段空值输出为[]
SerializerFeature.WriteNullStringAsEmpty, // 将字符串类型字段的空值输出为空字符串""
SerializerFeature.WriteNullNumberAsZero // 将数值类型字段的空值输出为0
);
fastJsonConverter.setFastJsonConfig(fastJsonConfig);
// 配置支持的数据类型,解决中文乱码:设置响应的content-type为application/json;charset=UTF-8
List<MediaType> fastMediaTypes = new ArrayList<>();
fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
fastJsonConverter.setSupportedMediaTypes(fastMediaTypes);
//3.添加FastJsonConverter到restTemplate
messageConverters.add(0,fastJsonConverter);
restTemplate.setMessageConverters(messageConverters);
return restTemplate;
}
}
3. RestTemplate アクセス要求
メソッドグループ | 説明 |
---|---|
getForObject |
GET 経由で表現を取得します。 |
getForEntity |
ResponseEntity GET を使用して、(つまり、ステータス、ヘッダー、および本文)を取得します。 |
headForHeaders |
HEAD を使用してリソースのすべてのヘッダーを取得します。 |
postForLocation |
POST を使用して新しいリソースを作成し、Location 応答からヘッダーを返します。 |
postForObject |
POST を使用して新しいリソースを作成し、応答から表現を返します。 |
postForEntity |
POST を使用して新しいリソースを作成し、応答から表現を返します。 |
put |
PUT を使用してリソースを作成または更新します。 |
patchForObject |
PATCH を使用してリソースを更新し、応答から表現を返します。JDK はHttpURLConnection をサポートしていませんPATCH が、Apache HttpComponents などはサポートしていることに注意してください。 |
delete |
DELETE を使用して、指定された URI のリソースを削除します。 |
optionsForAllow |
ALLOW を使用して、リソースに対して許可された HTTP メソッドを取得します。 |
exchange |
必要に応じてさらなる柔軟性を提供する、前述のメソッドのより一般化された (そしてあまり独自性のない) バージョン。これはRequestEntity (HTTP メソッド、URL、ヘッダー、および本文を入力として含む) を受け入れ、 を返します。これらのメソッドを使用すると、ジェネリックスで応答タイプを指定する代わりに をResponseEntity 使用できます。ParameterizedTypeReference Class |
execute |
リクエストを実行するための最も一般的な方法で、コールバック インターフェイスを介してリクエストの準備と応答の抽出を完全に制御します。 |
3.1 GETリクエスト
プロトコル リクエストの
RestTemplate
送信には、一般的に 2 つのメソッド タイプが使用されます。HTTP GET
getForObject()
: 戻り値は、HTTP
HttpMessageConverter が自動的に型変換を実行してオブジェクトをカプセル化した後に返される、プロトコルの応答本文に対応します。getForEntity()
: 返されるのは、応答をカプセル化したオブジェクトです。応答本文に加えて、ステータス コードやその他の応答情報も含まResponseEntity
れます。ResponseEntity
HTTP
HTTP
contentType、contentLength、Header
- getForEntity():
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String,?> uriVariables)
- url: the URL
- responseType: the type of the return value(ResponseEntity内的ResponseBody也自动由HttpMessageConverter进行了转换并封装进ResponseEntity,转换类型由responseType指定<T>)
- uriVariables: the map containing variables for the URI template
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)
- url: the URL
- responseType: the type of the return value
- uriVariables: the variables to expand the template
<T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType)
- url: the URL
- responseType: the type of the return value
- getForObject():
<T> T getForObject(String url, Class<T> responseType, Map<String,?> uriVariables)
- url: the URL
- responseType: the type of the return value
- uriVariables: the map containing variables for the URI template
<T> T getForObject(String url, Class<T> responseType, Object... uriVariables)
- url: the URL
- responseType: the type of the return value
- uriVariables: the variables to expand the template
<T> T getForObject(URI url, Class<T> responseType)
- url: the URL
- responseType: the type of the return value
@SpringBootTest
class ServerLinkerApplicationTests {
@Autowired
RestTemplate restTemplate;
@Test
void contextLoads() {
//1.无参GET请求(或直接在URL字符串中拼接参数):直接返回对象
String url1 = "http://localhost:8080/testGet";
ResponseBean responseBean1 = restTemplate.getForObject(url1, ResponseBean.class);
//2.带参GET请求:使用占位符传参(当路径变量有多个时,可以按照顺序依次传递)
String url2 = "http://localhost:8080/testGet/{1}/{2}";
//String url2 = "http://localhost:8080/testGet?userId={1}&startTime={2}";
ResponseBean responseBean2 = restTemplate.getForObject(url2,ResponseBean.class,"001","2022-09-02");
//3.带参GET请求:使用Map传参
String url3 = "http://localhost:8080/testGet?userId={user_Id}&startTime={start_Time}";
Map<String, String> params = new HashMap<>();
params.put("user_Id", "001");
params.put("start_Time", "2022-09-02");
ResponseBean responseBean3 = restTemplate.getForObject(url3,ResponseBean.class,params);
//4.getForEntity使用
String url4 = "http://localhost:8080/testGet";
ResponseEntity<ResponseBean> response = restTemplate.getForEntity(url4, ResponseBean.class);
// (1)获取响应体转换对象
ResponseBean responseBean = response.getBody();
// (2)获取response额外信息
HttpStatus statusCode = response.getStatusCode();
int statusCodeValue = response.getStatusCodeValue();
HttpHeaders headers = response.getHeaders();
System.out.println("HTTP 响应状态:" + statusCode);
System.out.println("HTTP 响应状态码:" + statusCodeValue);
System.out.println("HTTP Headers信息:" + headers);
}
}
3.2 POSTリクエスト
RestTemplate
プロトコル リクエストを送信することによりHTTP PSOT
、一般的に使用されるメソッド タイプが 2 つあります (Post データの転送には、もう 1 つのオブジェクト リクエスト パラメータのみが使用されます)。
postForObject()
: 戻り値は、HTTP
HttpMessageConverter が自動的に型変換を実行してオブジェクトをカプセル化した後に返される、プロトコルの応答本文に対応します。postForEntity()
: 返されるのは、応答をカプセル化したオブジェクトです。応答本文に加えて、ステータス コードやその他の応答情報も含まResponseEntity
れます。ResponseEntity
HTTP
HTTP
contentType、contentLength、Header
- postForEntity():
<T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Map<String,?> uriVariables)
- url: the URL
- request: the Object to be POSTed (may be null)(request传输对象会自动通过转换器Converter转换为JSON格式字符串传输)
- responseType: the type of the return value
- uriVariables: the variables to expand the URI template using the given map
<T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables)
- url: the URL
- request: the Object to be POSTed (may be null)
- responseType: the type of the return value
- uriVariables: the variables to expand the template
<T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType)
- url: the URL
- request: the Object to be POSTed (may be null)
- responseType: the type of the return value
- postForObject():
<T> T postForObject(String url, Object request, Class<T> responseType, Map<String,?> uriVariables)
- url - the URL
- request - the Object to be POSTed (may be null)(The request parameter can be a HttpEntity in order to add additional HTTP headers to the request.)
- responseType - the type of the return value
- uriVariables - the variables to expand the template(URI Template variables are expanded using the given map.)
<T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables)
- url - the URL
- request - the Object to be POSTed (may be null)
- responseType - the type of the return value
- uriVariables - the variables to expand the template
<T> T postForObject(URI url, Object request, Class<T> responseType)
- url - the URL
- request - the Object to be POSTed (may be null)
- responseType - the type of the return value
@SpringBootTest
class WeChatPusherApplicationTests {
@Autowired
RestTemplate restTemplate;
@Test
void PostTest(){
//1.简单POST请求(或直接在URL字符串中拼接参数):传递DataItem数据(body),直接返回对象
String url1 = "http://localhost:8080/testGet";
DataItem dataItem = new DataItem("Number","Red");
ResponseBean responseBean1 = restTemplate.postForObject(url1, dataItem,ResponseBean.class);
//2.带路径参数POST请求:使用占位符传参(当路径变量有多个时,可以按照顺序依次传递)
String url2 = "http://localhost:8080/testGet/{1}/{2}";
ResponseBean responseBean2 = restTemplate.postForObject(url2,dataItem,ResponseBean.class,"001","2022-09-02");
//3.带路径参数POST请求:使用Map传参
String url3 = "http://localhost:8080/testGet?userId={user_Id}&startTime={start_Time}";
Map<String, String> params = new HashMap<>();
params.put("user_Id", "001");
params.put("start_Time", "2022-09-02");
ResponseBean responseBean3 = restTemplate.postForObject(url3,dataItem,ResponseBean.class,params);
//4.postForEntity使用
String url4 = "http://localhost:8080/testGet";
ResponseEntity<ResponseBean> response = restTemplate.postForEntity(url4, dataItem,ResponseBean.class);
// (1)获取响应体转换对象
ResponseBean responseBean = response.getBody();
// (2)获取response额外信息
HttpStatus statusCode = response.getStatusCode();
int statusCodeValue = response.getStatusCodeValue();
HttpHeaders headers = response.getHeaders();
System.out.println("HTTP 响应状态:" + statusCode);
System.out.println("HTTP 响应状态码:" + statusCodeValue);
System.out.println("HTTP Headers信息:" + headers);
}
}
3. RestTemplate のソース コード分析 (POST を例にします)
POSTリクエストの実行プロセスは次のとおりです。
1. postForObject()メソッドを呼び出し、メソッド内でexecute()を呼び出します。
2.execute() メソッドは最終的に doExecute() メソッドを呼び出します。
- 接続の取得 createRequest(): URL とリクエスト メソッドに従って、基礎となる HTTP クライアントからリクエスト接続オブジェクトを取得します。
- 転送データの追加/変換 doWithRequest(): リクエストボディに変換データを追加 (オブジェクト -> コンバーター リスト -> JSON 文字列)
- 実行リクエストexecute():インターセプタ処理、レスポンス取得
- 応答例外処理 handleResponse(): ErrorHandle を呼び出す
- レスポンスデータ変換 extractData(): レスポンスBodyデータをresponseTypeに対応したデータに変換して返却(JSON String -> Converters List -> )
public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {
//...
//1.调用postForObject()方法:
// - 将传递body数据对象object request封装为一个RequestCallback对象
// - 调用execute方法执行post request的处理逻辑
public <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType)
throws RestClientException {
RequestCallback requestCallback = httpEntityCallback(request, responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<>(responseType, getMessageConverters());
return execute(url, HttpMethod.POST, requestCallback, responseExtractor);
}
//2.execute()方法最终会调用doExecute()方法:这里才是真正的整个post处理流程
// - 获取连接:根据url和请求方式从底层HTTP客户端中获取request连接对象
// - 附加/转换传递数据:执行doWithRequest()方法给请求body附加数据(object -> Converters List -> JSON String)
// - 执行请求:拦截器处理、获取响应
// - 响应异常处理:调用 ErrorHandle
// - 响应数据转换:将response Body数据转换为对应responseType的数据并返回(JSON String -> Converters List -> <T>)
@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 {
//(1)根据url和请求方式,从HTTP客户端(连接池)中获取连接对象request
ClientHttpRequest request = createRequest(url, method);
//(2)判断:如果post传递的body数据(一般为JSON)不为空,则执行doWithRequest()方法给请求附加参数(遍历HttpMessageConverters List,将传递的object request数据转换为JSON String字符串)
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
//(3)执行request请求(经过拦截器),并获取响应response
response = request.execute();
//(4)进行响应的后续处理:状态码判断、异常/错误处理(errorHandle)
handleResponse(url, method, response);
//(5)响应的数据处理与转换:调用responseExtractor.extractData()方法,遍历HttpMessageConverters List,将Body数据转换为对应responseType的数据并返回
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();
}
}
}
//3.doWithRequest()方法:转换/附加POST请求的Body数据
// - 获取object body数据对象
// - 遍历 messageConverter List,转换为JSON String数据
@Override
@SuppressWarnings("unchecked")
public void doWithRequest(ClientHttpRequest httpRequest) throws IOException {
super.doWithRequest(httpRequest);
//(1)获取传递的object body数据对象
Object requestBody = this.requestEntity.getBody();
//(2)判断requestbody是否为空,若为空则处理头header信息
if (requestBody == null) {
HttpHeaders httpHeaders = httpRequest.getHeaders();
HttpHeaders requestHeaders = this.requestEntity.getHeaders();
if (!requestHeaders.isEmpty()) {
requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values)));
}
if (httpHeaders.getContentLength() < 0) {
httpHeaders.setContentLength(0L);
}
}
//(3)若requestbody不为空,则遍历 messageConverter List,将Object数据对象转换为JSON String数据(GenericHttpMessageConverter的子类)
else {
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> genericConverter =
(GenericHttpMessageConverter<Object>) messageConverter;
if (genericConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) {
if (!requestHeaders.isEmpty()) {
requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values)));
}
logBody(requestBody, requestContentType, genericConverter);
genericConverter.write(requestBody, requestBodyType, requestContentType, httpRequest);
return;
}
}
else if (messageConverter.canWrite(requestBodyClass, requestContentType)) {
if (!requestHeaders.isEmpty()) {
requestHeaders.forEach((key, values) -> httpHeaders.put(key, new ArrayList<>(values)));
}
logBody(requestBody, requestContentType, messageConverter);
((HttpMessageConverter<Object>) messageConverter).write(
requestBody, requestContentType, httpRequest);
return;
}
}
String message = "No HttpMessageConverter for " + requestBodyClass.getName();
if (requestContentType != null) {
message += " and content type \"" + requestContentType + "\"";
}
throw new RestClientException(message);
}
}
}