How to use RestTemplate in Spring boot, part II
考虑到篇幅问题,这里将一篇文章切割成了两部分,我们将在How to use RestTemplate in Spring boot, part I的基础上继续介绍。
Consumer Service
pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.qwfys.sample</groupId>
<artifactId>maoshan</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.qwfys.sample</groupId>
<artifactId>maoshan-jurong</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.qwfys.sample</groupId>
<artifactId>maoshan-common</artifactId>
</dependency>
</dependencies>
</project>
Controller
package com.qwfys.sample.maoshan.jurong.controller;
import com.qwfys.sample.maoshan.common.vo.AccountDetailVO;
import com.qwfys.sample.maoshan.jurong.business.spec.ConsumerBusiness;
import com.qwfys.sample.maoshan.jurong.comon.result.MaoResultCode;
import com.qwfys.sample.maoshan.jurong.comon.result.MaoResult;
import com.qwfys.sample.maoshan.jurong.request.AccountDetailRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@Tag(name = "消费方管理")
public class ConsumerController {
@Autowired
private ConsumerBusiness consumerBusiness;
@PostMapping("/consumer/account/detail")
@Operation(summary = "获取消费方账号详情")
public MaoResult<AccountDetailVO> viewAccountDetail(@RequestHeader("Authorization") String token, @RequestBody AccountDetailRequest param) {
MaoResult<AccountDetailVO> result = null;
try {
AccountDetailVO detailVO = consumerBusiness.viewAccountDetail(token, param);
result = MaoResult.success(detailVO);
} catch (Exception e) {
log.error(e.getMessage(), e);
result = MaoResult.fail(MaoResultCode.EXCEPTION);
}
log.info("response: {}", result);
return result;
}
}
Business
ConsumerBusiness
package com.qwfys.sample.maoshan.jurong.business.spec;
import com.qwfys.sample.maoshan.common.vo.AccountDetailVO;
import com.qwfys.sample.maoshan.jurong.request.AccountDetailRequest;
/**
* @author liuwenke
* @since 0.0.1
*/
public interface ConsumerBusiness {
AccountDetailVO viewAccountDetail(String token, AccountDetailRequest param);
}
ConsumerBusinessImpl
package com.qwfys.sample.maoshan.jurong.business.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qwfys.sample.maoshan.common.result.HuaResult;
import com.qwfys.sample.maoshan.common.vo.AccountDetailVO;
import com.qwfys.sample.maoshan.jurong.business.spec.ConsumerBusiness;
import com.qwfys.sample.maoshan.jurong.request.AccountDetailRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;
/**
* @author liuwenke
* @since 0.0.1
*/
@Slf4j
@Service
public class ConsumerBusinessImpl implements ConsumerBusiness {
@Autowired
private RestTemplate restTemplate;
@Override
public AccountDetailVO viewAccountDetail(String token, AccountDetailRequest param) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", token);
headers.setContentType(MediaType.APPLICATION_PROBLEM_JSON);
ObjectMapper mapper = new ObjectMapper();
String body = null;
try {
body = mapper.writeValueAsString(param);
} catch (JsonProcessingException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
HttpEntity<?> httpEntity = new HttpEntity<>(body, headers);
String apiUrl = "http://127.0.0.1:19000/provider/account/detail";
HttpMethod httpMethod = HttpMethod.POST;
ResponseEntity<HuaResult<AccountDetailVO>> responseEntity = restTemplate.exchange(
apiUrl,
httpMethod,
httpEntity,
new ParameterizedTypeReference<HuaResult<AccountDetailVO>>() {
}
);
Assert.notNull(responseEntity, "responseEntity为空");
HuaResult<AccountDetailVO> huaResult = responseEntity.getBody();
Assert.notNull(huaResult, "result不能为空");
Assert.isTrue(huaResult.getIsSuccess(), "code:" + huaResult.getResultCode() + " message:" + huaResult.getResultMessage());
AccountDetailVO accountDetail = huaResult.getData();
return accountDetail;
}
}
Result
MaoResult
package com.qwfys.sample.maoshan.jurong.comon.result;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.util.Objects;
@Slf4j
@Builder
@Data
@AllArgsConstructor
@Schema(description = "统一返回实体")
public class MaoResult<T> {
/**
* 状态码
*/
private String code;
/**
* 信息
*/
private String msg;
/**
* 数据
*/
private T data;
public MaoResult() {
}
public boolean isSuccess() {
return Objects.equals(MaoResultCode.OK.value(), this.code);
}
public boolean isFail() {
return !Objects.equals(MaoResultCode.OK.value(), this.code);
}
public static <T> MaoResult<T> success(T data) {
MaoResult<T> maoResult = new MaoResult<>();
maoResult.setData(data);
maoResult.setCode(MaoResultCode.OK.value());
return maoResult;
}
public static <T> MaoResult<T> success() {
MaoResult<T> maoResult = new MaoResult<>();
maoResult.setCode(MaoResultCode.OK.value());
maoResult.setMsg(MaoResultCode.OK.getMsg());
return maoResult;
}
public static <T> MaoResult<T> success(Integer code, T data) {
return success(String.valueOf(code), data);
}
public static <T> MaoResult<T> success(String code, T data) {
MaoResult<T> maoResult = new MaoResult<>();
maoResult.setCode(code);
maoResult.setData(data);
return maoResult;
}
public static <T> MaoResult<T> showFailMsg(String msg) {
log.error(msg);
MaoResult<T> maoResult = new MaoResult<>();
maoResult.setMsg(msg);
maoResult.setCode(MaoResultCode.SHOW_FAIL.value());
return maoResult;
}
public static <T> MaoResult<T> fail(MaoResultCode maoResultCode) {
log.error(maoResultCode.toString());
MaoResult<T> maoResult = new MaoResult<>();
maoResult.setMsg(maoResultCode.getMsg());
maoResult.setCode(maoResultCode.value());
return maoResult;
}
public static <T> MaoResult<T> fail(MaoResultCode maoResultCode, T data) {
log.error(maoResultCode.toString());
MaoResult<T> maoResult = new MaoResult<>();
maoResult.setMsg(maoResultCode.getMsg());
maoResult.setCode(maoResultCode.value());
maoResult.setData(data);
return maoResult;
}
public static <T> MaoResult<T> fail(String code, String msg, T data) {
log.error(msg);
MaoResult<T> maoResult = new MaoResult<>();
maoResult.setMsg(msg);
maoResult.setCode(code);
maoResult.setData(data);
return maoResult;
}
public static <T> MaoResult<T> fail(String code, String msg) {
return fail(code, msg, null);
}
public static <T> MaoResult<T> fail(Integer code, T data) {
MaoResult<T> maoResult = new MaoResult<>();
maoResult.setCode(String.valueOf(code));
maoResult.setData(data);
return maoResult;
}
}
MaoResultCode
package com.qwfys.sample.maoshan.jurong.comon.result;
import lombok.ToString;
/**
* @author liuwenke
* @since 0.0.1
*/
@ToString
public enum MaoResultCode {
OK("00000", "ok"),
SHOW_FAIL("A00001", ""),
REMOTE_CALL_FAIL("REMOTE_CALL_FAIL", "远程接口调用失败"),
PARSING_JSON_FAIL("PARSING_JSON_FAIL", "JSON解析失败"),
/**
* 用于直接显示提示系统的成功,内容由输入内容决定
*/
SHOW_SUCCESS("A00002", ""),
/**
* 未授权
*/
UNAUTHORIZED("A00004", "Unauthorized"),
/**
* 服务器出了点小差
*/
EXCEPTION("A00005", "服务器出了点小差"),
/**
* 方法参数没有校验,内容由输入内容决定
*/
METHOD_ARGUMENT_NOT_VALID("A00014", "方法参数没有校验");
private final String code;
private final String msg;
public String value() {
return code;
}
public String getMsg() {
return msg;
}
MaoResultCode(String code, String msg) {
this.code = code;
this.msg = msg;
}
}
Request
package com.qwfys.sample.maoshan.jurong.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
@Schema(description = "账号详情请求参数")
@Data
@Accessors(chain = true)
public class AccountDetailRequest {
@Schema(description = "用户ID")
private Long userId;
}
Config
application.yml
mybatis-plus:
global-config:
banner: false
server:
port: 19001
spring:
datasource:
url: jdbc:mysql://127.0.0.1:23306/jurong?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
username: root
password: Gah6kuP7ohfio4
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
hikari:
minimum-idle: 0
maximum-pool-size: 20
idle-timeout: 10000
auto-commit: true
connection-test-query: SELECT 1
JurongConfig
package com.qwfys.sample.maoshan.jurong.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
/**
* @author liuwenke
* @since 0.0.1
*/
@Configuration
public class JurongConfig {
private static final int CONNECT_TIMEOUT = 8000;
private static final int SOCKET_TIMEOUT = 8000;
@Bean
public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
//factory.setConnectTimeout(CONNECT_TIMEOUT);
//factory.setReadTimeout(SOCKET_TIMEOUT);
return new RestTemplate(factory);
//return new RestTemplate();
}
}
knife4jConfig
package com.qwfys.sample.maoshan.jurong.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author liuwenke
* @since 0.0.1
*/
@Configuration
public class knife4jConfig {
@Bean
public GroupedOpenApi baseRestApi() {
return GroupedOpenApi.builder()
.group("接口文档")
.packagesToScan("com.qwfys.sample.maoshan")
.build();
}
@Bean
public OpenAPI springShopOpenApi() {
return new OpenAPI()
.info(
new Info()
.title("Service Consumer接口文档")
.description("Service Consumer接口文档")
.version("0.0.1-SNAPSHOT")
.license(
new License()
.name("使用请遵守MIT License授权协议")
.url("https://github.com/ab-sample/maoshan")
)
);
}
}
Summary
1、借助RestTemplate取到json数据以后,很多时候,我们都期望将json数据反序列化为Java Bean对象。
如果Java Bean不包含泛型,可以借助如下方法完成数据的接收与反序列化:
public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {
//...
@Override
public <T> ResponseEntity<T> exchange(String url, HttpMethod method,
@Nullable HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables)
throws RestClientException {
RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables));
}
//...
}
将json反序列化后的Java Bean对应的class传给方法的参数responseType。
如果Java Bean包含泛型,可以借助如下方法完成数据的接收与反序列化:
public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {
//...
@Override
public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity,
ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException {
Type type = responseType.getType();
RequestCallback requestCallback = httpEntityCallback(requestEntity, type);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(type);
return nonNull(execute(url, method, requestCallback, responseExtractor, uriVariables));
}
//...
}
这个时候,我们要实例化一个ParameterizedTypeReference的对象出来,实例化的时候,需要将json反序列化后的Java Bean做为ParameterizedTypeReference的泛型参数来实例化,如:
public AccountDetailVO viewAccountDetail(String token, AccountDetailRequest param) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", token);
headers.setContentType(MediaType.APPLICATION_PROBLEM_JSON);
ObjectMapper mapper = new ObjectMapper();
String body = null;
try {
body = mapper.writeValueAsString(param);
} catch (JsonProcessingException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
HttpEntity<?> httpEntity = new HttpEntity<>(body, headers);
String apiUrl = "http://127.0.0.1:19000/provider/account/detail";
HttpMethod httpMethod = HttpMethod.POST;
ResponseEntity<HuaResult<AccountDetailVO>> responseEntity = restTemplate.exchange(
apiUrl,
httpMethod,
httpEntity,
new ParameterizedTypeReference<HuaResult<AccountDetailVO>>() {
}
);
Assert.notNull(responseEntity, "responseEntity为空");
HuaResult<AccountDetailVO> huaResult = responseEntity.getBody();
Assert.notNull(huaResult, "result不能为空");
Assert.isTrue(huaResult.getIsSuccess(), "code:" + huaResult.getResultCode() + " message:" + huaResult.getResultMessage());
AccountDetailVO accountDetail = huaResult.getData();
return accountDetail;
}
这里将HuaResult<AccountDetailVO>
做为ParameterizedTypeReference的泛型参数传入,完成实例化。
2、如果要对RestTemplate实例做定制化,在创建的时候,可以基于相应的工厂方法实现,如:
@Configuration
public class JurongConfig {
private static final int CONNECT_TIMEOUT = 8000;
private static final int SOCKET_TIMEOUT = 8000;
@Bean
public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(CONNECT_TIMEOUT);
factory.setReadTimeout(SOCKET_TIMEOUT);
return new RestTemplate(factory);
//return new RestTemplate();
}
}
如果只是简单用一下,可以用如下方式完成实例化:
@Configuration
public class JurongConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
完整的样例代码我放到github上的maoshan代码仓库了,如果想看完整的代码可以到github下载。