How to use RestTemplate in Spring boot, part I
Overview
在项目中,我们经常会遇到需要从另外一个系统中以http方式获取数据的情况,这种需求,大部分人可能会采用Apache HttpClient来实现,也有一些人会采用OkHttp或者其他的组件来实现。
在Spring boot中,Spring boot为我们提供了一个名称为RestTemplate的http客户端组件,基于它,我们也是可以完成上述诉求的。关于RestTemplate的用法,大部分人都是熟悉的,这里不再赘述,今天我们着重介绍一下,面对数据提供方基于泛型生成的json字符串,我们如何将其反序列化为完整的Java Bean对象。
Goal
这里有服务提供方Provider响应服务时生成的数据的json的片段:
{
"isSuccess": true,
"resultCode": "00000",
"resultMessage": "操作成功!",
"data": {
"userId": "1639178930477944839",
"userName": "welcome",
"fullName": "welcome",
"telephone": "13800000000",
"dept": null,
"roles": [
{
"id": "11",
"code": "ADMIN",
"name": "管理员"
},
{
"id": "12",
"code": "BUYER",
"name": "买家"
},
{
"id": "13",
"code": "SELLER",
"name": "商家"
}
]
}
}
我们希望服务消费方取到数据以后,能将json反序列化为对应的Java Bean。下面我们就从服务提供方、服务消费方两个方向以一个实际的案例详细介绍这类问题的通用解决方法。
Maoshan
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0</version>
<relativePath/>
</parent>
<groupId>com.qwfys.sample</groupId>
<artifactId>maoshan</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>maoshan</name>
<description>maoshan</description>
<modules>
<module>maoshan-common</module>
<module>maoshan-huayang</module>
<module>maoshan-jurong</module>
</modules>
<properties>
<java.version>17</java.version>
<com.qwfys.sample.maoshan.version>0.0.1-SNAPSHOT</com.qwfys.sample.maoshan.version>
<knife4j-openapi3-jakarta-spring-boot-starter.version>4.0.0
</knife4j-openapi3-jakarta-spring-boot-starter.version>
<hutool.version>5.8.12</hutool.version>
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.qwfys.sample</groupId>
<artifactId>maoshan-common</artifactId>
<version>${com.qwfys.sample.maoshan.version}</version>
</dependency>
<dependency>
<groupId>com.qwfys.sample</groupId>
<artifactId>maoshan-huayang</artifactId>
<version>${com.qwfys.sample.maoshan.version}</version>
</dependency>
<dependency>
<groupId>com.qwfys.sample</groupId>
<artifactId>maoshan-jurong</artifactId>
<version>${com.qwfys.sample.maoshan.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-extra</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-jwt</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-poi</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-cache</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-db</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>${knife4j-openapi3-jakarta-spring-boot-starter.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Common 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-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</project>
DTO
AccountDetailDTO
package com.qwfys.sample.maoshan.common.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @author liuwenke
* @since 0.0.1
*/
@Schema(description = "账号详情请求参数")
@Data
@Accessors(chain = true)
public class AccountDetailDTO {
@Schema(description = "用户ID")
private Long userId;
}
VO
AccountDetailVO
package com.qwfys.sample.maoshan.common.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
@Schema(description = "账号详情")
@Data
@Accessors(chain = true)
public class AccountDetailVO {
@Schema(description = "用户ID")
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long userId;
@Schema(description = "用户名称")
private String userName;
@Schema(description = "姓名")
private String fullName;
@Schema(description = "电话号码")
private String telephone;
@Schema(description = "所属部门")
private DeptVO dept;
@Schema(description = "角色列表")
private List<RoleVO> roles;
}
DeptVO
package com.qwfys.sample.maoshan.common.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
@Schema(description = "部门")
@Data
@Accessors(chain = true)
public class DeptVO {
@JsonFormat(shape = JsonFormat.Shape.STRING)
@Schema(description = "部门ID")
private Long id;
@Schema(description = "部门名称")
private String name;
@Schema(description = "部门路径")
private String path;
}
RoleVO
package com.qwfys.sample.maoshan.common.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
@Schema(description = "角色")
@Data
@Accessors(chain = true)
public class RoleVO {
@JsonFormat(shape = JsonFormat.Shape.STRING)
@Schema(description = "角色ID")
private Long id;
@Schema(description = "角色编码")
private String code;
@Schema(description = "角色名称")
private String name;
}
Result
HuaResult
package com.qwfys.sample.maoshan.common.result;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpStatus;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
/**
* @author liuwenke
* @since 0.0.1
*/
@Builder
@Data
@AllArgsConstructor
@Schema(description = "统一返回实体")
public class HuaResult<T> {
@Schema(description = "是否成功")
private Boolean isSuccess = false;
@Schema(description = "响应码")
private String resultCode;
@Schema(description = "消息")
private String resultMessage;
@Schema(description = "数据")
private T data;
public HuaResult() {
this.resultCode = StrUtil.toString(HttpStatus.HTTP_OK);
this.resultMessage = "";
this.data = null;
}
public HuaResult(Boolean isSuccess, int resultCode, T data, String resultMessage) {
this.isSuccess=isSuccess;
this.resultCode = StrUtil.toString(resultCode);
this.data = data;
this.resultMessage = resultMessage;
}
// 错误时的构造器
public HuaResult(int resultCode, T data, String resultMessage) {
this.resultCode = StrUtil.toString(resultCode);
this.data = data;
this.resultMessage = resultMessage;
}
private HuaResult(IResultCode resultCode) {
this(resultCode.isSuccess(), resultCode.resultCode(), resultCode.resultMessage(), null);
}
private HuaResult(IResultCode resultCode, String message) {
this(resultCode.isSuccess(), resultCode.resultCode(), message, null);
}
private HuaResult(IResultCode resultCode, T data) {
this(resultCode.isSuccess(), resultCode.resultCode(), resultCode.resultMessage(), data);
}
private HuaResult(IResultCode resultCode, String message, T data) {
this(resultCode.isSuccess(), resultCode.resultCode(), message, data);
}
public static <T> HuaResult<T> data(T data) {
return data(HuaResultCode.SUCCESS.resultMessage, data);
}
public static <T> HuaResult<T> data(String message, T data) {
return data(HuaResultCode.SUCCESS.isSuccess(), HuaResultCode.SUCCESS.resultCode(), message, data);
}
public static <T> HuaResult<T> data(boolean isSuccess, String code, String message, T data) {
return new HuaResult<>(isSuccess, code, null == data ? HuaResultCode.SUCCESS.resultCode : message, data);
}
public static <T> HuaResult<T> success() {
return new HuaResult<>(HuaResultCode.SUCCESS);
}
public static <T> HuaResult<T> success(IResultCode resultCode) {
return new HuaResult<>(resultCode);
}
public static <T> HuaResult<T> success(String message) {
return new HuaResult<>(HuaResultCode.SUCCESS, message);
}
public static <T> HuaResult<T> success(IResultCode resultCode, String message) {
return new HuaResult<>(resultCode, message);
}
public static <T> HuaResult<T> fail() {
return new HuaResult<>(HuaResultCode.FAILURE);
}
public static <T> HuaResult<T> fail(IResultCode resultCode) {
return new HuaResult<>(resultCode);
}
public static <T> HuaResult<T> fail(IResultCode resultCode, String message) {
return new HuaResult<>(resultCode, message);
}
public static <T> HuaResult<T> fail(String message) {
return new HuaResult<>(HuaResultCode.FAILURE, message);
}
public static <T> HuaResult<T> judge(boolean flag) {
return flag ? success(HuaResultCode.SUCCESS.resultMessage()) : fail(HuaResultCode.FAILURE.resultMessage());
}
public static <T> HuaResult<T> judge(int flag) {
return flag > 0 ? success(HuaResultCode.SUCCESS.resultMessage()) : fail(HuaResultCode.FAILURE.resultMessage());
}
}
HuaResultCode
package com.qwfys.sample.maoshan.common.result;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
/**
* @author liuwenke
* @since 0.0.1
*/
@AllArgsConstructor
@NoArgsConstructor
public enum HuaResultCode implements IResultCode {
/**
* SpringMVC默认级别的异常封装
*/
METHOD_NOT_SUPPORT(false, "405", "请求方法类型不支持,请检查请求类型!"),
MEDIA_TYPE_NOT_SUPPORT(false, "405", "HTTP媒体类型不支持异常!"),
MEDIA_TYPE_NOT_ACCEPTABLE(false, "406", "客户端请求期望响应的媒体类型与服务器响应的媒体类型不一致!"),
MISSING_PATH_VARIABLE(false, "500", "缺少可选的路径参数!"),
MISSING_SERVLET_REQUEST_PARAMETER(false, "400", "缺少请求参数!"),
REQUEST_BINDING(false, "400", "请求参数不合法,请联系管理员检查前端配置"),
CONVERSION_NOT_SUPPORTED(false, "500", "参数绑定异常!"),
TYPE_MIS_MATCH(false, "400", "类型不匹配异常!"),
MESSAGE_NOT_READABLE(false, "400", "消息不可读异常!"),
MESSAGE_NOT_WRITABLE(false, "500", "消息不可写异常!"),
METHOD_ARGUMENT_NOT_VALID(false, "400", "请求参数不合法!"),
MISSING_SERVLET_REQUEST_PART(false, "400", ""),
BIND(false, "400", "请求参数不合法!"),
NO_HANDLER_FOUND(false, "404", "没有找到合适的处理器,处理器可能不存在,请检查路径是否正确!"),
ASYNC_REQUEST_TIMEOUT(false, "503", "异步请求超时"),
/**
* 不可预知系统异常
*/
SERVER_ERROR(false, "99999", "抱歉,系统繁忙,请稍后重试!"),
/**
* 基本的返回
*/
SUCCESS(true, "00000", "操作成功!"),
FAILURE(false, "A0001", "操作失败!"),
PARMERROR(false, "A0002", "请求参数不合法!"),
NO_DATA(false, "A0003", "暂无数据"),
NO_FILE(false, "A0004", "没有文件"),
NO_PERMISSION_FILE(false, "A0005", "没有文件权限"),
LOGIN_FAIL(false, "A0006", "用户不存在或被禁用"),
DATA_EXCEPTION(false, "A0007", "数据异常"),
/**
* 系统可预知异常
*/
UN_KNOW(false, "A0100", "未知错误"),
NULL_POINT(false, "A0101", "空指针异常"),
HTTP_MESSAGE_NOT_READABLE(false, "A0102", "http请求参数转换异常,参数格式错误"),
HTTP_REQUEST_METHOD_NOT_SUPPORTED(false, "A0103", "http请求方式不支持"),
/**
* 参数相关
*/
PARAM_ERROR(false, "A0200", "用户请求参数错误"),
ENUM_NOT_EXIST(false, "A0201", "枚举名不存在"),
ENUM_PARSE_EXIST(false, "A0202", "获取所有枚举过程中解析失败,请联系开发人员解决"),
ADD_NOT_NEED_ID(false, "A0203", "新增的时候主键不需要传递"),
UPDATE_ID_NOT_NULL(false, "A0204", "编辑的时候不需要传递主键"),
MISSING_ANNO_PARAM(false, "A0205", "注解缺失必要的参数"),
;
public Boolean isSuccess;
public String resultCode;
public String resultMessage;
@Override
public Boolean isSuccess() {
return this.isSuccess;
}
@Override
public String resultCode() {
return this.resultCode;
}
@Override
public String resultMessage() {
return this.resultMessage;
}
@Override
public void formateMessage(String... messages) {
this.resultMessage = StrUtil.format(this.resultMessage, messages);
}
}
IResultCode
package com.qwfys.sample.maoshan.common.result;
/**
* @author liuwenke
* @since 0.0.1
*/
public interface IResultCode {
/**
* 是否成功
*
* @return
*/
Boolean isSuccess();
/**
* 返回状态码
*
* @return
*/
String resultCode();
/**
* 状态消息描述
*
* @return
*/
String resultMessage();
/**
* 重写message
*
* @param messages
* @return
*/
void formateMessage(String... messages);
}
Provider 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-huayang</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.qwfys.sample</groupId>
<artifactId>maoshan-common</artifactId>
</dependency>
</dependencies>
</project>
Controller
ProviderController
package com.qwfys.sample.maoshan.huayang.controler;
import com.qwfys.sample.maoshan.common.dto.AccountDetailDTO;
import com.qwfys.sample.maoshan.common.result.HuaResult;
import com.qwfys.sample.maoshan.common.vo.AccountDetailVO;
import com.qwfys.sample.maoshan.huayang.business.spec.AccountBusiness;
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.util.Assert;
import org.springframework.web.bind.annotation.*;
/**
* @author liuwenke
* @since 0.0.1
*/
@Slf4j
@RestController
@Tag(name = "服务方管理")
public class ProviderController {
@Autowired
private AccountBusiness accountBusiness;
@ResponseBody
@PostMapping("/provider/account/detail")
@Operation(summary = "获取服务方账号详情")
public HuaResult<AccountDetailVO> findAccountDetail(@RequestHeader("Authorization") String token, @RequestBody AccountDetailDTO dto) {
Assert.notNull(token, "token不能为空");
Assert.notNull(dto.getUserId(), "userID不能为空");
AccountDetailVO detailVO = accountBusiness.findAccountDetail();
HuaResult<AccountDetailVO> huaResult = HuaResult.data(detailVO);
log.info("response: {}", huaResult);
return huaResult;
}
}
Business
AccountBusiness
package com.qwfys.sample.maoshan.huayang.business.spec;
import com.qwfys.sample.maoshan.common.vo.AccountDetailVO;
/**
* @author liuwenke
* @since 0.0.1
*/
public interface AccountBusiness {
AccountDetailVO findAccountDetail();
}
AccountBusinessImpl
package com.qwfys.sample.maoshan.huayang.business.impl;
import com.qwfys.sample.maoshan.common.vo.AccountDetailVO;
import com.qwfys.sample.maoshan.common.vo.RoleVO;
import com.qwfys.sample.maoshan.huayang.business.spec.AccountBusiness;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* @author liuwenke
* @since 0.0.1
*/
@Service
public class AccountBusinessImpl implements AccountBusiness {
@Override
public AccountDetailVO findAccountDetail() {
List<RoleVO> roles = new ArrayList<>();
roles.add(
new RoleVO()
.setId(11L)
.setCode("ADMIN")
.setName("管理员")
);
roles.add(
new RoleVO()
.setId(12L)
.setCode("BUYER")
.setName("买家")
);
roles.add(
new RoleVO()
.setId(13L)
.setCode("SELLER")
.setName("商家")
);
AccountDetailVO detailVO = new AccountDetailVO()
.setUserId(1639178930477944839L)
.setUserName("welcome")
.setFullName("welcome")
.setTelephone("13800000000")
.setRoles(roles);
return detailVO;
}
}
config
application.yml
mybatis-plus:
global-config:
banner: false
server:
port: 19000
spring:
datasource:
url: jdbc:mysql://127.0.0.1:23306/huayang?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
knife4jConfig
package com.qwfys.sample.maoshan.huayang.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 Provider接口文档")
.description("Service Provider接口文档")
.version("0.0.1-SNAPSHOT")
.license(
new License()
.name("使用请遵守MIT License授权协议")
.url("https://github.com/ab-sample/maoshan")
)
);
}
}
未完,考虑到篇幅问题,剩余部分放在How to use RestTemplate in Spring boot, part II中了,如果要阅读可以移步到第二部分阅读。