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 {@Autowiredprivate 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 {@Autowiredprivate RestTemplate restTemplate;@Overridepublic 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: falseserver:port: 19001spring: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=trueusername: rootpassword: Gah6kuP7ohfio4driver-class-name: com.mysql.cj.jdbc.Drivertype: com.zaxxer.hikari.HikariDataSourcehikari:minimum-idle: 0maximum-pool-size: 20idle-timeout: 10000auto-commit: trueconnection-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;@Beanpublic 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 {@Beanpublic GroupedOpenApi baseRestApi() {return GroupedOpenApi.builder().group("接口文档").packagesToScan("com.qwfys.sample.maoshan").build();}@Beanpublic 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 {@Overridepublic <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 {@Overridepublic <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;@Beanpublic 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 {@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}
}
完整的样例代码我放到github上的maoshan代码仓库了,如果想看完整的代码可以到github下载。