序文
日々の開発では、内部サービス間の呼び出しであろうとサードパーティ サービス間の呼び出しであろうと、HTTP リクエストを開始することは避けられません。Java で Http リクエストを開始する一般的な方法には、ネイティブ HttpURLConnection、Apache の HttpClient、Spring の RestTemplate などが含まれます。 Spring フレームワークに基づいている場合は、RestTemplate を使用することを強くお勧めします。理由は非常に簡単です。これは、Postman を使用するのと同じように、http リクエストを開始する習慣と非常に一致しており、特定の内容のみを考慮する必要があります。 URL、ヘッダー、本文など、面倒な詳細については RestTemplate を使用して (パッケージを) 明確に配置するのに役立ちます。無関係な詳細について心配する必要はありません。特に、RestTemplate.exchange メソッドは、一手で他のメソッドを倒すことができると言えます。。。そこで、この記事では RestTemplate.exchange のさまざまな使用法を詳細に紹介し、日常の開発におけるさまざまなシナリオをカバーできるように努めます。
1. リクエストの取得
以下に 5 つの一般的なシナリオを示します。1.1 基本型を返す 1.2 カスタム オブジェクト型を返す 1.3 リスト型を返す 1.4 マップ型を返す 1.5 カスタム ジェネリック型を返す
1.1 基本型を返す
まず基本的な API をシミュレートしましょう。ユーザー ID に従って名前 呼び出しコードを取得します。
// 1.1 get请求返回基本类型
@GetMapping("/name")
public String getName(@RequestParam("id") Integer id) {
String url = "http://localhost:8080/demo/name/mock?id=" + id;
return restTemplate.exchange(url, HttpMethod.GET, null, String.class).getBody();
}
呼び出されたモックコード:
@GetMapping("/name/mock")
public String mockName(@RequestParam("id") Integer id) {
return "天罡" + id;
}
Verify:やっぱり、簡潔に言うべきですね~~
http://localhost:8080/demo/name?id=123 をリクエストし、Tiangang 123 を返します
交換のメソッドパラメータの説明について: ソースコードのコメントを見るだけで非常に明確ですが、それは余分です。
1.2 カスタム オブジェクト タイプを返す
実際、カスタム オブジェクトは String 呼び出しと同じです。只需要将返回类型String.class改成DTO.class即可
たとえば、ユーザー ID に従ってユーザー情報を取得します。
新しい UserDto オブジェクトを作成します。
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserDto implements Serializable {
private Integer id;
private String name;
private Integer age;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date birthday;
}
電話番号:
// 1.2 get请求返回对象类型
@GetMapping("/user")
public UserDto getUser(@RequestParam("id") Integer id) {
String url = "http://localhost:8080/demo/user/mock?id=" + id;
return restTemplate.exchange(url, HttpMethod.GET, null, UserDto.class).getBody();
}
呼び出されたモックコード:
@GetMapping("/user/mock")
public UserDto mockUser(@RequestParam("id") Integer id) {
return UserDto.builder().id(id)
.name("天罡" + id)
.age(id + 18)
.birthday(new Date()).build();
}
確認してください: OK~~
http://localhost:8080/demo/user?id=1 をリクエストすると、{ "id": 1、"name": "Tiangang"、"age": 19、"birthday": "2022-11-06 05: 35:43”}
1.3 戻り値リストの型
ジェネリック型の場合、ジェネリック型を交換する別のオーバーロードされたメソッドを使用する必要があります。つまり将responseType换成ParameterizedTypeReference
、ソース コードのコメントを読むことをお勧めします。
次に、一般的なシナリオのシミュレーションを続けます。ユーザー名に基づいて一致するすべてのユーザーをファジー検索し、返される結果は複数であり、リスト タイプを使用します。電話番号:通过ParameterizedTypeReference指定返回的List
// 1.3 get请求返回List<T>类型
@GetMapping("/user/list")
public List<UserDto> getUserList(@RequestParam("name") String name) {
String url = "http://localhost:8080/demo/user/list/mock?name=" + name;
ParameterizedTypeReference<List<UserDto>> responseBodyType = new ParameterizedTypeReference<List<UserDto>>() {
};
return restTemplate.exchange(url, HttpMethod.GET, null, responseBodyType).getBody();
}
呼び出されたモックコード:
@GetMapping("/user/list/mock")
public List<UserDto> mockUserList(@RequestParam("name") String name) {
List<UserDto> list = new ArrayList<>();
for (int i = 1; i < 3; i++) {
list.add(UserDto.builder().id(i)
.name(name + i)
.age(i + 10)
.birthday(new Date()).build());
}
return list;
}
確認してください: OK~~
リクエスト http://localhost:8080/demo/user/list?name=天港 return [ { "id": 1, "name": "天港 1", "age": 11, "birthday": "2022-11 -06 21:44:24” }, { “id”: 2, “名前”: “天港2”, “年齢”: 12, “誕生日”: “2022-11-06 21:44:24” } ]
1.4 Map<K,V> 型を返す
Map もジェネリック型であり、K と Vの 2 つの型があります。一般的なシナリオのシミュレーションを続けます:キーワード検索によると、異なる型は異なるフィールドを返します。返される結果フィールドは固定されていないため、Map 型を返します。電話番号:依然通过ParameterizedTypeReference指定返回的Map
// 1.4 get请求返回Map类型
@GetMapping("/user/map")
public Map<String, Object> getUserMap(@RequestParam(value = "type", required = true) Integer type, @RequestParam("key") String key) {
String url = "http://localhost:8080/demo/user/map/mock?type=" + type + "&key=" + key;
ParameterizedTypeReference<Map<String, Object>> responseBodyType = new ParameterizedTypeReference<Map<String, Object>>() {
};
return restTemplate.exchange(url, HttpMethod.GET, null, responseBodyType).getBody();
}
呼び出されたモックコード:
@GetMapping("/user/map/mock")
public Map<String, Object> mockUserMap(@RequestParam(value = "type", required = true) Integer type, @RequestParam("key") String key) {
Map<String, Object> map = new HashMap<>();
if (type.equals(1)) {
map.put("id", 1);
map.put("name" + type, "hello" + key);
} else {
map.put("id", 2);
map.put("name" + type, "hello" + key);
}
return map;
}
確認してください:さまざまなタイプに応じてさまざまなフィールドが返されます。美しい~~
http://localhost:8080/demo/user/map?type=1&key=123 をリクエストすると、{ “id”: 1, “
name1
”: “hello123” }が返されます。http://localhost:8080/demo/user/map?type=2&key=456 をリクエストすると、{ “id”: 2, “
name2
”: “hello456” }が返されます。
1.5 カスタムジェネリック型を返す
1.2 でユーザー ID に基づいてユーザー情報を取得し、カスタム オブジェクト タイプを返すシナリオをシミュレートしましたが、次に、カスタム カスタム コードの一般的な戻り結果をカスタマイズし、1.2 にいくつかの機能強化を加えます。ユーザー ID に従ってユーザーを取得します。情報、状況に応じて異なるコードが返されます。未处理非法请求、异常等情况
新しい Result クラスを作成します。
@Data
public class Result<T extends Serializable> implements Serializable {
private boolean success;
private String code;
private String message;
private T data;
public static <T extends Serializable> Result<T> success(String code, String message, T data) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
result.setData(data);
result.setSuccess(true);
return result;
}
public static <T extends Serializable> Result<T> success(T data) {
return success("200", "成功", data);
}
public static <T extends Serializable> Result<T> fail(String code, String message) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
result.setSuccess(false);
return result;
}
}
電話番号:依然通过ParameterizedTypeReference指定返回的Result<T>
// 1.5 get请求返回自定义泛型类型
@GetMapping("/user/result")
public Result<UserDto> getUserResult(@RequestParam("id") Integer id) {
String url = "http://localhost:8080/demo/user/result/mock?id=" + id;
ParameterizedTypeReference<Result<UserDto>> responseBodyType = new ParameterizedTypeReference<Result<UserDto>>() {
};
return restTemplate.exchange(url, HttpMethod.GET, null, responseBodyType).getBody();
}
呼び出されたモックコード:
@GetMapping("/user/result/mock")
public Result<UserDto> mockUserResult(@RequestParam("id") Integer id) {
if (id == null || id <= 0) {
return Result.fail("400", "id不合法!");
}
if (id % 2 == 0) {
// 这里只是模拟异常情况
return Result.fail("500", "操作失败,访问量太大了!");
}
UserDto userDto = UserDto.builder().id(id)
.name("天罡" + id)
.age(id + 18)
.birthday(new Date()).build();
return Result.success("200", "成功", userDto);
}
チェックしてみてください。まさに私たちが望んでいたもの、まさに期待どおりです。
リクエスト http://localhost:8080/demo/user/result?id=0 return { "成功": false、"コード": "400"、"メッセージ": "ID が無効です!"、"データ": null }
http://localhost:8080/demo/user/result?id=1 をリクエストすると、{ "success": true、"code": "200"、"message": "success"、"data": { "id" が返されます。 : 1, "名前": "天港1"、"年齢": 19、"誕生日": "2022-11-07 04:03:09" } }
リクエスト http://localhost:8080/demo/user/result?id=2 return { "success": false, "code": "500", "message": "操作が失敗しました。アクセス数が多すぎます!", "data ": ヌル }
2. リクエストを投稿する
実際、交換の場合、 POST と GET の使用法は非常に似ているため、ここではheader と body を渡す方法を示すデモが 2 つだけ用意されています。2.1 ヘッダーとボディを渡してオブジェクト型を返す 2.2 ヘッダーとボディを渡してカスタム ジェネリック型を返す
2.1 ヘッダー+ボディの戻りオブジェクト型を渡す
電話番号:
@GetMapping("/user/body")
public UserDto postUser(@RequestParam("id") Integer id) {
String url = "http://localhost:8080/demo/user/body/mock";
UserDto body = UserDto.builder().id(id)
.name("body" + id)
.age(id + 18)
.birthday(new Date()).build();
// header根据实际情况设置,没有就空着
HttpHeaders headers = new HttpHeaders();
headers.add("AccessKey", "自定义的API访问key");
headers.add("Content-Type", "application/json");
HttpEntity<?> requestEntity = new HttpEntity<>(body, headers);
return restTemplate.exchange(url, HttpMethod.POST, requestEntity, UserDto.class).getBody();
}
呼び出されたモックコード:
@PostMapping("/user/body/mock")
public UserDto mockPostUser(@RequestBody UserDto userParam) {
return userParam;
}
確認してください: OK~~
http://localhost:8080/demo/user/body?id=1 をリクエストすると、{ "id": 1、"name": "body1"、"age": 19、"birthday": "2022-11-06" が返されます。 21:20:41”}
2.2 ヘッダーとボディを渡してカスタム ジェネリック型を返す
通常の型を返す場合との違いは、将responseType换成ParameterizedTypeReference
呼び出しコードです。
@GetMapping("/user/result/body")
public Result<UserDto> postUserResult(@RequestParam("id") Integer id) {
String url = "http://localhost:8080/demo/user/result/body/mock";
UserDto body = UserDto.builder().id(id)
.name("body" + id)
.age(id + 10)
.birthday(new Date()).build();
// header根据实际情况设置,没有就空着
HttpHeaders headers = new HttpHeaders();
headers.add("AccessKey", "自定义的API访问key");
headers.add("Content-Type", "application/json");
HttpEntity<?> requestEntity = new HttpEntity<>(body, headers);
ParameterizedTypeReference<Result<UserDto>> responseBodyType = new ParameterizedTypeReference<Result<UserDto>>(){
};
return restTemplate.exchange(url, HttpMethod.POST, requestEntity, responseBodyType).getBody();
}
呼び出されたモックコード:
@PostMapping("/user/result/body/mock")
public Result<UserDto> mockPostUserResult(@RequestBody UserDto userParam) {
return Result.success("200", "成功", userParam);
}
確認してください: OK~~
http://localhost:8080/demo/user/body?id=1 をリクエストすると、{ "success": true、"code": "200"、"message": "success"、"data": { "id" が返されます。 : 1, "名前": "体1"、"年齢": 11、"誕生日": "2022-11-06 21:25:25" } }
3. 異常事態への対応
上記の例外はいずれも例外を処理しません。通常、次の 2 つの例外を処理します。
- RestClientException自体をスローします
- 返された ResponseEntity のコードが 200 に等しくありません
一般的なタイプ:
public <T> T restForEntity(HttpMethod httpMethod, String url, HttpHeaders headers, Object body
, Class<T> responseType) {
HttpEntity<?> requestEntity = null;
if (headers != null || body != null) {
requestEntity = new HttpEntity<>(body, headers);
}
try {
ResponseEntity<T> responseEntity = restTemplate.exchange(url, httpMethod, requestEntity, responseType);
if (responseEntity.getStatusCode().equals(HttpStatus.OK)) {
return responseEntity.getBody();
} else {
// 处理Code不等于200的情况
System.out.println("返回结果不等于200:code=" + responseEntity.getStatusCode().value()
+ " reason=" + responseEntity.getStatusCode().getReasonPhrase());
}
} catch (RestClientException e) {
// 处理RestClientException
e.printStackTrace();
}
return null;
}
汎用タイプ: 只需要将普通类型的入参Class<T>改成 ParameterizedTypeReference<T>
public <T> T restForWarpEntity(HttpMethod httpMethod, String url, HttpHeaders headers, Object body
, ParameterizedTypeReference<T> responseBodyType) {
HttpEntity<?> requestEntity = null;
if (headers != null || body != null) {
requestEntity = new HttpEntity<>(body, headers);
}
try {
ResponseEntity<T> responseEntity = restTemplate.exchange(url, httpMethod, requestEntity, responseBodyType);
if (responseEntity.getStatusCode().equals(HttpStatus.OK)) {
return responseEntity.getBody();
} else {
// 处理Code不等于200的情况, 这里只简单打印
System.out.println("返回结果不等于200:code=" + responseEntity.getStatusCode().value()
+ " reason=" + responseEntity.getStatusCode().getReasonPhrase());
}
} catch (RestClientException e) {
// 处理RestClientException, 这里只简单打印
e.printStackTrace();
}
return null;
}
4. RestTemplate配置@Bean
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);
restTemplate.getMessageConverters()
.stream()
.filter(MappingJackson2HttpMessageConverter.class::isInstance)
.map(MappingJackson2HttpMessageConverter.class::cast)
.findFirst()
.map(MappingJackson2HttpMessageConverter::getObjectMapper)
.ifPresent(objectMapper -> {
// 去掉默认的时间戳格式
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
// 设置为东八区
objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
// 序列化时,日期的统一格式
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
// 忽略大小写
objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
});
return restTemplate;
}
@Bean
public ClientHttpRequestFactory clientHttpRequestFactory(){
// 如果使用okHttpClient需要引入jar包:okhttp
// OkHttp3ClientHttpRequestFactory factory = new OkHttp3ClientHttpRequestFactory();
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(15000);
factory.setReadTimeout(30000);
return factory;
}
}