Java服務(wù)調(diào)用RestTemplate與HttpClient的使用詳解
概述
常見的遠程調(diào)用方式有以下2種:
- RPC: Remote Produce Call遠程過程調(diào)用,類似的還有RMI(remote method invoke)。自定義數(shù)據(jù)格式,基于原生TCP通信,速度快,效率高。早期的webservice,現(xiàn)在熱門的dubbo,都是RPC的典型代表。
- Http: http其實是一種網(wǎng)絡(luò)傳輸協(xié)議,基于TCP,規(guī)定了數(shù)據(jù)傳輸?shù)母袷健?現(xiàn)在客戶端瀏覽器與服務(wù)端通信基本都是采用Http協(xié)議,也可以用來進行遠程服務(wù)調(diào)用。缺點是消息封裝臃腫,優(yōu)勢是對服務(wù)的提供和調(diào)用方?jīng)]有任何技術(shù)限定,自由靈活,更符合微服務(wù)理念?,F(xiàn)在熱門的Rest風(fēng)格,就可以通過http協(xié)議來實現(xiàn)。
如果項目全部采用 Java技術(shù)棧,那么使用Dubbo作為微服務(wù)架構(gòu)是一個不錯的選擇。
如果項目的技術(shù)棧多樣化,主要采用了Spring和SpringBoot框架,那么SpringCloud搭建微服務(wù)是不二之選,使用Http方式來實現(xiàn)服務(wù)間調(diào)用。
java開發(fā)中,使用http連接,訪問第三方網(wǎng)絡(luò)接口,通常使用的連接工具為RestTemplate、HttpClient和OKHttp。
RestTemplate
概述及依賴
HttpClient和OKHttp兩種連接工具,使用起來比較復(fù)雜,如果使用spring框架,可以使用restTemplate來進行http連接請求。
restTemplate默認的連接方式是java中的HttpConnection,可以使用ClientHttpRequestFactory指定不同的HTTP連接方式。
依賴
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.7</version>
</dependency>
配置類
基礎(chǔ)配置
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(150 * 1000); // ms
factory.setConnectTimeout(150 * 1000); // ms
return factory;
}
}
進階配置
import org.apache.http.client.HttpClient;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
/**
* http連接管理器
*/
@Bean
public HttpClientConnectionManager poolingHttpClientConnectionManager() {
/*// 注冊http和https請求
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory.getSocketFactory())
.build();
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(registry);*/
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
// 最大連接數(shù)
poolingHttpClientConnectionManager.setMaxTotal(500);
// 同路由并發(fā)數(shù)(每個主機的并發(fā))
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(100);
return poolingHttpClientConnectionManager;
}
/**
* HttpClient
* @param poolingHttpClientConnectionManager
*/
@Bean
public HttpClient httpClient(HttpClientConnectionManager poolingHttpClientConnectionManager) {
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
// 設(shè)置http連接管理器
httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
/*// 設(shè)置重試次數(shù),默認是3次,沒有開啟
httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true));*/
// 設(shè)置默認請求頭
/*List<Header> headers = new ArrayList<>();
headers.add(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36"));
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"));
httpClientBuilder.setDefaultHeaders(headers);*/
return httpClientBuilder.build();
}
/**
* 請求連接池配置
* @param httpClient
*/
@Bean
public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) {
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
// httpClient創(chuàng)建器
clientHttpRequestFactory.setHttpClient(httpClient);
// 連接超時時間/毫秒(連接上服務(wù)器(握手成功)的時間,超出拋出connect timeout)
clientHttpRequestFactory.setConnectTimeout(5 * 1000);
// 數(shù)據(jù)讀取超時時間(socketTimeout)/毫秒(服務(wù)器返回數(shù)據(jù)(response)的時間,超過拋出read timeout)
clientHttpRequestFactory.setReadTimeout(10 * 1000);
// 從連接池獲取請求連接的超時時間,不宜過長,必須設(shè)置/毫秒(超時間未拿到可用連接,會拋出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool)
clientHttpRequestFactory.setConnectionRequestTimeout(10 * 1000);
return clientHttpRequestFactory;
}
/**
* rest模板
*/
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
// 配置請求工廠
return new RestTemplate(clientHttpRequestFactory);
}
}使用
實體類
@Data
@Builder
@NoArgsConstrutor
@AllArgsConstrutor
public class BaseResponse<TempUser> implements Serializable {
private static final long serialVersionUID = 1L;
private String responseCode;
private String responseMessage;
private List<TempUser> responseData;
}@Data
@Builder
@NoArgsConstrutor
@AllArgsConstrutor
public class TempUser implements Serializable {
private static final long serialVersionUID = 1L;
private String userName;
private Integer age;
}
GET請求
普通訪問
BaseResponse result = restTemplate.getForObject(
"http://localhost:8080/cs-admin/rest/getUser?userName=張三&age=18", BaseResponse.class);
返回HTTP狀態(tài)
ResponseEntity<BaseResponse> responseEntity = restTemplate.getForEntity(
"http://localhost:8080/cs-admin/rest/getUser?userName=張三&age=18", TempUser.class);
// 獲取狀態(tài)對象
HttpStatus httpStatus = responseEntity.getStatusCode();
// 獲取狀態(tài)碼
int statusCodeValue = responseEntity.getStatusCodeValue();
// 獲取headers
HttpHeaders httpHeaders = responseEntity.getHeaders();
// 獲取body
BaseResponse result = responseEntity.getBody();
映射請求參數(shù)
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("userName", "張三");
paramMap.put("age", 18);
BaseResponse result = restTemplate.getForObject(
"http://localhost:8080/cs-admin/rest/getUser?userName={userName}&age={age}",
BaseResponse.class, paramMap);
POST請求
普通訪問接口
TempUser param = new TempUser();
param.setUserName("張三");
param.setAge(18);
BaseResponse result = restTemplate.postForObject("http://localhost:8080/cs-admin/rest/getPostUser",
param, BaseResponse.class);
帶HEAD訪問接口
// 請求頭信息
HttpHeaders headers = new HttpHeaders();
//headers.setContentType(MediaType.valueOf("application/json;charset=UTF-8"));
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
//headers.add("headParam1", "headParamValue");
// 請求體內(nèi)容
TempUser param = new TempUser();
param.setUserName("張三");
param.setAge(18);
// 組裝請求信息
HttpEntity<TempUser> httpEntity=new HttpEntity<>(param, headers);
BaseResponse result = restTemplate.postForObject("http://localhost:8080/cs-admin/rest/getPostUser",
httpEntity, BaseResponse.class);無請求體的訪問:僅method為post,傳參方式仍然為get的param方式
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("userName", "張三");
paramMap.put("age", 18);
BaseResponse result = restTemplate.postForObject(
"http://localhost:8080/cs-admin/rest/getPostUserNoBody?userName={userName}&age={age}",
null, BaseResponse.class, paramMap);
System.out.println(result);
上傳文件
后臺接口代碼:
@RequestMapping("uploadFile")
public TempUser uploadFile(HttpServletRequest request, TempUser form) {
MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) request;
//獲取文件信息
MultipartFile multipartFile = multipartHttpServletRequest.getFile("file");
TempUser tempUser = new TempUser();
if (multipartFile != null) {
tempUser.setUserName(form.getUserName() + " " + multipartFile.getOriginalFilename());
}
if(form!=null){
tempUser.setAge(form.getAge());
}
return tempUser;
}
訪問方式:
// 文件
FileSystemResource file=new FileSystemResource("D:\\Elasticsearch權(quán)威指南(中文版).pdf");
// 設(shè)置請求內(nèi)容
MultiValueMap<String, Object> param=new LinkedMultiValueMap<>();
param.add("file", file);
// 其他參數(shù)
param.add("userName", "張三");
param.add("age", 18);
// 組裝請求信息
HttpEntity<MultiValueMap<String, Object>> httpEntity=new HttpEntity<>(param);
// 發(fā)送請求
TempUser result = restTemplate.postForObject("http://localhost:8080/cs-admin/rest/uploadFile",
httpEntity, TempUser.class);
HttpClient
概述
HttpClient 通過連接池創(chuàng)建連接:
- 管理連接的基本單位是Route(路由),每個路由上都會維護一定數(shù)量的HTTP連接
- 每次調(diào)用后必須執(zhí)行 releaseConnection
- 路由可以理解為客戶端機器到目標(biāo)機器的一條線路
- 如果不給 httpclient配置指定的連接管理器,httpclient會自動使用PoolingHttpClientConnectionManager作為連接管理器。
- PoolingHttpClientConnectionManager默認的maxConnPerRoute和maxConnTotal分別是是2和20。也就是對于每個服務(wù)器最多只會維護2個連接,看起來有點少。所以,在日常使用時我們盡量使用自己配置的連接管理器比較好。
連接池:
- 連接池技術(shù)作為創(chuàng)建和管理連接的緩沖池技術(shù)。
- 連接池管理的對象是長連接
- 有長連接的優(yōu)勢
**長連接:**是指客戶端與服務(wù)器端一旦建立連接以后,可以進行多次數(shù)據(jù)傳輸而不需重新建立連接,
優(yōu)勢:
- 省去了每次數(shù)據(jù)傳輸連接建立的時間開銷
- 資源的訪問控制
**短連接:**每次數(shù)據(jù)傳輸都需要客戶端和服務(wù)器端建立一次連接
使用
使用HttpClient發(fā)送請求、接收響應(yīng)很簡單,一般需要如下幾步即可:
- 創(chuàng)建
HttpClient對象 - 創(chuàng)建請求方法的實例,并指定請求
URL。如果需要發(fā)送GET請求,創(chuàng)建HttpGet對象;如果需要發(fā)送POST請求,創(chuàng)建HttpPost對象。 - 如果需要發(fā)送請求參數(shù),可調(diào)用
HttpGet、HttpPost共同的setParams(HetpParams params)方法來添加請求參數(shù);對于HttpPost對象而言,也可調(diào)用setEntity(HttpEntity entity)方法來設(shè)置請求參數(shù),參數(shù)則必須用NameValuePair[]數(shù)組存儲 - 調(diào)用
HttpClient對象的execute(HttpUriRequest request)發(fā)送請求,該方法返回一個HttpResponse - 調(diào)用
HttpResponse的getAllHeaders()、getHeaders(String name)等方法可獲取服務(wù)器的響應(yīng)頭;調(diào)用HttpResponse的getEntity()方法可獲取HttpEntity對象,該對象包裝了服務(wù)器的響應(yīng)內(nèi)容。程序可通過該對象獲取服務(wù)器的響應(yīng)內(nèi)容 - 釋放連接。無論執(zhí)行方法是否成功,都必須釋放連接
依賴:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient-cache</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.2</version>
</dependency>
java工具類
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* HttpClient 工具類
*/
@Slf4j
public class HttpClientUtil {
public static final String APPLICATION_JSON_VALUE = "application/json";
private static final Logger logger = log;
private static final Integer CONN_TIME_OUT = 3000;// 超時時間豪秒
private static final Integer SOCKET_TIME_OUT = 10000;
/** 每個路由的最大請求數(shù),默認2 */
private static final Integer DEFAULT_MAX_PER_ROUTE = 40;
/** 最大連接數(shù),默認20 */
private static final Integer MAX_TOTAL = 400;
private static HttpClient httpClient;
static {
// 請求配置
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(CONN_TIME_OUT)
.setConnectionRequestTimeout(CONN_TIME_OUT)
.setSocketTimeout(SOCKET_TIME_OUT)
.build();
// 管理 http連接池
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setDefaultMaxPerRoute(DEFAULT_MAX_PER_ROUTE);
cm.setMaxTotal(MAX_TOTAL);
httpClient = HttpClients.custom()
.setConnectionManager(cm)
.setDefaultRequestConfig(requestConfig)
.build();
}
/**
* Get請求
*/
public static String requestGet(String url, Map<String, String> paramsMap) throws Exception {
logger.info("GET request url:{} params:{}", url, paramsMap);
Long start = System.currentTimeMillis();
List<NameValuePair> params = initParams(paramsMap);
// Get請求
HttpGet httpGet = new HttpGet(url);
try {
// 設(shè)置參數(shù)
String str = EntityUtils.toString(new UrlEncodedFormEntity(params, StandardCharsets.UTF_8));
String uriStr = StringUtils.isEmpty(str) ?
httpGet.getURI().toString() : httpGet.getURI().toString() + "?" + str;
httpGet.setURI(new URI(uriStr));
// 發(fā)送請求
HttpResponse response = httpClient.execute(httpGet);
logger.info("GET request url:{} response:{} time:{}",
url, response, System.currentTimeMillis() - start);
// 獲取返回數(shù)據(jù)
return getSuccessRetFromResp(response, url, JSON.toJSONString(paramsMap));
} finally {
// 必須釋放連接,不然連接用完后會阻塞
httpGet.releaseConnection();
}
}
/**
* Post請求,Map格式數(shù)據(jù)
*/
public static String requestPost(String url, Map<String, String> paramsMap) throws Exception {
logger.info("POST request url:{} params:{}", url, paramsMap);
Long start = System.currentTimeMillis();
List<NameValuePair> params = initParams(paramsMap);
HttpPost httpPost = new HttpPost(url);
try {
httpPost.setEntity(new UrlEncodedFormEntity(params, Consts.UTF_8));
HttpResponse response = httpClient.execute(httpPost);
logger.info("POST request url:{} response:{} time:{}",
url, response, System.currentTimeMillis() - start);
String retStr = getSuccessRetFromResp(response, url, JSON.toJSONString(paramsMap));
return retStr;
} finally {
httpPost.releaseConnection();
}
}
/**
* Post請求,json格式數(shù)據(jù)
*
*/
public static String requestPostJsonStr(String url, String json) throws Exception {
logger.info("POST request url:{} params:{}", url, json);
long start = System.currentTimeMillis();
HttpPost httpPost = new HttpPost(url);
try {
StringEntity entity = new StringEntity(json, Consts.UTF_8);
entity.setContentType(APPLICATION_JSON_VALUE);
httpPost.setEntity(entity);
HttpResponse response = httpClient.execute(httpPost);
logger.info("POST request url:{} response:{} time:{}",
url, response, System.currentTimeMillis() - start);
return getSuccessRetFromResp(response, url, json);
} finally {
// 資源釋放
httpPost.releaseConnection();
}
}
/**
* post Object格式數(shù)據(jù)
*/
public static String requestPostJson(String url, Object obj) throws Exception {
String params = JSON.toJSONString(obj);
return requestPostJsonStr(url, params);
}
private static String getSuccessRetFromResp(HttpResponse response, String url, String params) throws Exception {
String retStr = "";
// 檢驗狀態(tài)碼,如果成功接收數(shù)據(jù)
int code = response.getStatusLine().getStatusCode();
if (code == 200) {
retStr = EntityUtils.toString(response.getEntity(), Consts.UTF_8);
} else {
throw new RuntimeException(String.format("Http request error:%s, url:%s, params:%s", response, url, params));
}
logger.info("Http request retStr:{}. url:{}", retStr, url);
return retStr;
}
private static List<NameValuePair> initParams(Map<String, String> paramsMap) {
List<NameValuePair> params = new ArrayList<NameValuePair>();
if (paramsMap == null)
return params;
for (Map.Entry<String, String> entry : paramsMap.entrySet()) {
params.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
return params;
}
}到此這篇關(guān)于Java服務(wù)調(diào)用RestTemplate與HttpClient的使用詳解的文章就介紹到這了,更多相關(guān)Java RestTemplate與HttpClient內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- restTemplate實現(xiàn)跨服務(wù)API調(diào)用方式
- Spring?Cloud?Alibaba?Nacos服務(wù)治理平臺服務(wù)注冊、RestTemplate實現(xiàn)微服務(wù)之間訪問負載均衡訪問的問題
- SpringCloud基于RestTemplate微服務(wù)項目案例解析
- springcloud中Ribbon和RestTemplate實現(xiàn)服務(wù)調(diào)用與負載均衡
- 關(guān)于springboot 中使用httpclient或RestTemplate做MultipartFile文件跨服務(wù)傳輸?shù)膯栴}
- restTemplate未設(shè)置連接數(shù)導(dǎo)致服務(wù)雪崩問題以及解決
相關(guān)文章
小程序與后端Java接口交互實現(xiàn)HelloWorld入門
本文主要介紹了小程序與后端Java接口交互實現(xiàn)HelloWorld入門 ,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07
Springboot設(shè)置文件上傳大小限制的實現(xiàn)示例
Spring Boot工程嵌入的tomcat限制了請求的文件大小默認為1MB,單次請求的文件的總數(shù)不能大于10Mb,本文主要介紹了Springboot設(shè)置文件上傳大小限制的實現(xiàn)示例,感興趣的可以了解一下2023-11-11
idea2020.1設(shè)置多個spring boot的service啟動的實現(xiàn)
這篇文章主要介紹了idea2020.1設(shè)置多個spring boot的service啟動,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06
Java利用FileUtils讀取數(shù)據(jù)和寫入數(shù)據(jù)到文件
這篇文章主要介紹了Java利用FileUtils讀取數(shù)據(jù)和寫入數(shù)據(jù)到文件,下面文章圍繞FileUtils的相關(guān)資料展開怎么讀取數(shù)據(jù)和寫入數(shù)據(jù)到文件的內(nèi)容,具有一定的參考價值,徐婭奧德小伙伴可以參考一下2021-12-12

