Java服務(wù)調(diào)用RestTemplate與HttpClient的使用詳解
概述
常見的遠(yuǎn)程調(diào)用方式有以下2種:
- RPC: Remote Produce Call遠(yuǎn)程過程調(diào)用,類似的還有RMI(remote method invoke)。自定義數(shù)據(jù)格式,基于原生TCP通信,速度快,效率高。早期的webservice,現(xiàn)在熱門的dubbo,都是RPC的典型代表。
- Http: http其實(shí)是一種網(wǎng)絡(luò)傳輸協(xié)議,基于TCP,規(guī)定了數(shù)據(jù)傳輸?shù)母袷健?現(xiàn)在客戶端瀏覽器與服務(wù)端通信基本都是采用Http協(xié)議,也可以用來進(jìn)行遠(yuǎn)程服務(wù)調(diào)用。缺點(diǎn)是消息封裝臃腫,優(yōu)勢(shì)是對(duì)服務(wù)的提供和調(diào)用方?jīng)]有任何技術(shù)限定,自由靈活,更符合微服務(wù)理念?,F(xiàn)在熱門的Rest風(fēng)格,就可以通過http協(xié)議來實(shí)現(xiàn)。
如果項(xiàng)目全部采用 Java技術(shù)棧,那么使用Dubbo作為微服務(wù)架構(gòu)是一個(gè)不錯(cuò)的選擇。
如果項(xiàng)目的技術(shù)棧多樣化,主要采用了Spring和SpringBoot框架,那么SpringCloud搭建微服務(wù)是不二之選,使用Http方式來實(shí)現(xiàn)服務(wù)間調(diào)用。
java開發(fā)中,使用http連接,訪問第三方網(wǎng)絡(luò)接口,通常使用的連接工具為RestTemplate、HttpClient和OKHttp。
RestTemplate
概述及依賴
HttpClient和OKHttp兩種連接工具,使用起來比較復(fù)雜,如果使用spring框架,可以使用restTemplate來進(jìn)行http連接請(qǐng)求。
restTemplate默認(rèn)的連接方式是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; } }
進(jìn)階配置
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() { /*// 注冊(cè)http和https請(qǐng)求 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ù)(每個(gè)主機(jī)的并發(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ù),默認(rèn)是3次,沒有開啟 httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true));*/ // 設(shè)置默認(rèn)請(qǐng)求頭 /*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(); } /** * 請(qǐng)求連接池配置 * @param httpClient */ @Bean public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) { HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(); // httpClient創(chuàng)建器 clientHttpRequestFactory.setHttpClient(httpClient); // 連接超時(shí)時(shí)間/毫秒(連接上服務(wù)器(握手成功)的時(shí)間,超出拋出connect timeout) clientHttpRequestFactory.setConnectTimeout(5 * 1000); // 數(shù)據(jù)讀取超時(shí)時(shí)間(socketTimeout)/毫秒(服務(wù)器返回?cái)?shù)據(jù)(response)的時(shí)間,超過拋出read timeout) clientHttpRequestFactory.setReadTimeout(10 * 1000); // 從連接池獲取請(qǐng)求連接的超時(shí)時(shí)間,不宜過長(zhǎng),必須設(shè)置/毫秒(超時(shí)間未拿到可用連接,會(huì)拋出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool) clientHttpRequestFactory.setConnectionRequestTimeout(10 * 1000); return clientHttpRequestFactory; } /** * rest模板 */ @Bean public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) { // 配置請(qǐng)求工廠 return new RestTemplate(clientHttpRequestFactory); } }
使用
實(shí)體類
@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請(qǐng)求
普通訪問
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)對(duì)象 HttpStatus httpStatus = responseEntity.getStatusCode(); // 獲取狀態(tài)碼 int statusCodeValue = responseEntity.getStatusCodeValue(); // 獲取headers HttpHeaders httpHeaders = responseEntity.getHeaders(); // 獲取body BaseResponse result = responseEntity.getBody();
映射請(qǐng)求參數(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請(qǐng)求
普通訪問接口
TempUser param = new TempUser(); param.setUserName("張三"); param.setAge(18); BaseResponse result = restTemplate.postForObject("http://localhost:8080/cs-admin/rest/getPostUser", param, BaseResponse.class);
帶HEAD訪問接口
// 請(qǐng)求頭信息 HttpHeaders headers = new HttpHeaders(); //headers.setContentType(MediaType.valueOf("application/json;charset=UTF-8")); headers.setContentType(MediaType.APPLICATION_JSON_UTF8); //headers.add("headParam1", "headParamValue"); // 請(qǐng)求體內(nèi)容 TempUser param = new TempUser(); param.setUserName("張三"); param.setAge(18); // 組裝請(qǐng)求信息 HttpEntity<TempUser> httpEntity=new HttpEntity<>(param, headers); BaseResponse result = restTemplate.postForObject("http://localhost:8080/cs-admin/rest/getPostUser", httpEntity, BaseResponse.class);
無請(qǐng)求體的訪問:僅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);
上傳文件
后臺(tái)接口代碼:
@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è)置請(qǐng)求內(nèi)容 MultiValueMap<String, Object> param=new LinkedMultiValueMap<>(); param.add("file", file); // 其他參數(shù) param.add("userName", "張三"); param.add("age", 18); // 組裝請(qǐng)求信息 HttpEntity<MultiValueMap<String, Object>> httpEntity=new HttpEntity<>(param); // 發(fā)送請(qǐng)求 TempUser result = restTemplate.postForObject("http://localhost:8080/cs-admin/rest/uploadFile", httpEntity, TempUser.class);
HttpClient
概述
HttpClient 通過連接池創(chuàng)建連接:
- 管理連接的基本單位是Route(路由),每個(gè)路由上都會(huì)維護(hù)一定數(shù)量的HTTP連接
- 每次調(diào)用后必須執(zhí)行 releaseConnection
- 路由可以理解為客戶端機(jī)器到目標(biāo)機(jī)器的一條線路
- 如果不給 httpclient配置指定的連接管理器,httpclient會(huì)自動(dòng)使用PoolingHttpClientConnectionManager作為連接管理器。
- PoolingHttpClientConnectionManager默認(rèn)的maxConnPerRoute和maxConnTotal分別是是2和20。也就是對(duì)于每個(gè)服務(wù)器最多只會(huì)維護(hù)2個(gè)連接,看起來有點(diǎn)少。所以,在日常使用時(shí)我們盡量使用自己配置的連接管理器比較好。
連接池:
- 連接池技術(shù)作為創(chuàng)建和管理連接的緩沖池技術(shù)。
- 連接池管理的對(duì)象是長(zhǎng)連接
- 有長(zhǎng)連接的優(yōu)勢(shì)
**長(zhǎng)連接:**是指客戶端與服務(wù)器端一旦建立連接以后,可以進(jìn)行多次數(shù)據(jù)傳輸而不需重新建立連接,
優(yōu)勢(shì):
- 省去了每次數(shù)據(jù)傳輸連接建立的時(shí)間開銷
- 資源的訪問控制
**短連接:**每次數(shù)據(jù)傳輸都需要客戶端和服務(wù)器端建立一次連接
使用
使用HttpClient
發(fā)送請(qǐng)求、接收響應(yīng)很簡(jiǎn)單,一般需要如下幾步即可:
- 創(chuàng)建
HttpClient
對(duì)象 - 創(chuàng)建請(qǐng)求方法的實(shí)例,并指定請(qǐng)求
URL
。如果需要發(fā)送GET
請(qǐng)求,創(chuàng)建HttpGet
對(duì)象;如果需要發(fā)送POST
請(qǐng)求,創(chuàng)建HttpPost
對(duì)象。 - 如果需要發(fā)送請(qǐng)求參數(shù),可調(diào)用
HttpGet、HttpPost
共同的setParams(HetpParams params)
方法來添加請(qǐng)求參數(shù);對(duì)于HttpPost
對(duì)象而言,也可調(diào)用setEntity(HttpEntity entity)
方法來設(shè)置請(qǐng)求參數(shù),參數(shù)則必須用NameValuePair[]
數(shù)組存儲(chǔ) - 調(diào)用
HttpClient
對(duì)象的execute(HttpUriRequest request)
發(fā)送請(qǐng)求,該方法返回一個(gè)HttpResponse
- 調(diào)用
HttpResponse的getAllHeaders()
、getHeaders(String name)
等方法可獲取服務(wù)器的響應(yīng)頭;調(diào)用HttpResponse
的getEntity()
方法可獲取HttpEntity
對(duì)象,該對(duì)象包裝了服務(wù)器的響應(yīng)內(nèi)容。程序可通過該對(duì)象獲取服務(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;// 超時(shí)時(shí)間豪秒 private static final Integer SOCKET_TIME_OUT = 10000; /** 每個(gè)路由的最大請(qǐng)求數(shù),默認(rèn)2 */ private static final Integer DEFAULT_MAX_PER_ROUTE = 40; /** 最大連接數(shù),默認(rèn)20 */ private static final Integer MAX_TOTAL = 400; private static HttpClient httpClient; static { // 請(qǐng)求配置 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請(qǐng)求 */ 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請(qǐng)求 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ā)送請(qǐng)求 HttpResponse response = httpClient.execute(httpGet); logger.info("GET request url:{} response:{} time:{}", url, response, System.currentTimeMillis() - start); // 獲取返回?cái)?shù)據(jù) return getSuccessRetFromResp(response, url, JSON.toJSONString(paramsMap)); } finally { // 必須釋放連接,不然連接用完后會(huì)阻塞 httpGet.releaseConnection(); } } /** * Post請(qǐng)求,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請(qǐng)求,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 = ""; // 檢驗(yàn)狀態(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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- restTemplate實(shí)現(xiàn)跨服務(wù)API調(diào)用方式
- Spring?Cloud?Alibaba?Nacos服務(wù)治理平臺(tái)服務(wù)注冊(cè)、RestTemplate實(shí)現(xiàn)微服務(wù)之間訪問負(fù)載均衡訪問的問題
- SpringCloud基于RestTemplate微服務(wù)項(xiàng)目案例解析
- springcloud中Ribbon和RestTemplate實(shí)現(xiàn)服務(wù)調(diào)用與負(fù)載均衡
- 關(guān)于springboot 中使用httpclient或RestTemplate做MultipartFile文件跨服務(wù)傳輸?shù)膯栴}
- restTemplate未設(shè)置連接數(shù)導(dǎo)致服務(wù)雪崩問題以及解決
相關(guān)文章
小程序與后端Java接口交互實(shí)現(xiàn)HelloWorld入門
本文主要介紹了小程序與后端Java接口交互實(shí)現(xiàn)HelloWorld入門 ,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07Springboot設(shè)置文件上傳大小限制的實(shí)現(xiàn)示例
Spring Boot工程嵌入的tomcat限制了請(qǐng)求的文件大小默認(rèn)為1MB,單次請(qǐng)求的文件的總數(shù)不能大于10Mb,本文主要介紹了Springboot設(shè)置文件上傳大小限制的實(shí)現(xiàn)示例,感興趣的可以了解一下2023-11-11java基于dom4j包實(shí)現(xiàn)對(duì)XML解析的方法
這篇文章主要介紹了java基于dom4j包實(shí)現(xiàn)對(duì)XML解析的方法,結(jié)合實(shí)例形式分析了java針對(duì)xml格式數(shù)據(jù)的相關(guān)解析操作實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-05-05Java實(shí)現(xiàn)經(jīng)典俄羅斯方塊游戲
俄羅斯方塊是一個(gè)最初由阿列克謝帕吉特諾夫在蘇聯(lián)設(shè)計(jì)和編程的益智類視頻游戲。本文將利用Java實(shí)現(xiàn)這一經(jīng)典的小游戲,需要的可以參考一下2022-01-01idea2020.1設(shè)置多個(gè)spring boot的service啟動(dòng)的實(shí)現(xiàn)
這篇文章主要介紹了idea2020.1設(shè)置多個(gè)spring boot的service啟動(dòng),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06Netty源碼分析NioEventLoop線程的啟動(dòng)
這篇文章主要為大家介紹了Netty源碼分析NioEventLoop線程的啟動(dòng)示例,有需要的朋友,可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03Java利用FileUtils讀取數(shù)據(jù)和寫入數(shù)據(jù)到文件
這篇文章主要介紹了Java利用FileUtils讀取數(shù)據(jù)和寫入數(shù)據(jù)到文件,下面文章圍繞FileUtils的相關(guān)資料展開怎么讀取數(shù)據(jù)和寫入數(shù)據(jù)到文件的內(nèi)容,具有一定的參考價(jià)值,徐婭奧德小伙伴可以參考一下2021-12-12