微信公衆號:跟着老萬學java
歡迎關注,瞭解更多編程技巧,一塊兒交流,一塊兒成長。html
RestTemplate是一種更優雅的調用RESTful服務的方式,而且能結合Ribbon一塊兒使用。那麼,你真的會使用它嗎?java
一、各類請求的傳參有什麼注意事項?web
二、怎麼傳遞json格式參數spring
三、怎麼修改底層的http鏈接方式,怎麼添加線程池配置apache
四、怎麼結合ribbon實現負載均衡調用編程
五、怎麼自定義轉換器json
若是這些你都不瞭解,那麼這篇文章多是你須要的。
api
概述
spring框架提供的RestTemplate類可用於在應用中調用rest服務,它簡化了與http服務的通訊方式,統一了RESTful的標準,封裝了http連接, 咱們只須要傳入url及返回值類型便可。相較於以前經常使用的HttpClient,RestTemplate是一種更優雅的調用RESTful服務的方式。微信
在Spring應用程序中訪問第三方REST服務與使用Spring RestTemplate類有關。RestTemplate類的設計原則與許多其餘Spring *模板類(例如JdbcTemplate、JmsTemplate)相同,爲執行復雜任務提供了一種具備默認行爲的簡化方法。併發
RestTemplate默認依賴JDK提供http鏈接的能力(HttpURLConnection),若是有須要的話也能夠經過setRequestFactory方法替換爲例如 Apache HttpComponents、Netty或OkHttp等其它HTTP library。
考慮到RestTemplate類是爲調用REST服務而設計的,所以它的主要方法與REST的基礎緊密相連就不足爲奇了,後者是HTTP協議的方法:HEAD、GET、POST、PUT、DELETE和OPTIONS。例如,RestTemplate類具備headForHeaders()、getForObject()、postForObject()、put()和delete()等方法。
實現
最新api地址:
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html
RestTemplate包含如下幾個部分:
HttpMessageConverter 對象轉換器
ClientHttpRequestFactory 默認是JDK的HttpURLConnection
ResponseErrorHandler 異常處理
ClientHttpRequestInterceptor 請求攔截器
get請求
請求接口
@GetMapping("/users")
public String getUser1(String username, HttpServletRequest request){
return "獲取用戶"+username+"的信息";
}
@GetMapping("/users/{username}")
public String getUser2(@PathVariable String username){
return "獲取用戶"+username+"的信息";
}
get請求
@Test
public void getRequestTest(){
RestTemplate template = new RestTemplate();
String url = "http://127.0.0.1:8080/users?username={username}";
String url2 = "http://127.0.0.1:8080/users/laowan";
//一、使用getForObject請求接口, 順序傳入參數
String result1 = template.getForObject(url, String.class, "laowan");
System.out.println("result1====================" + result1);
//二、使用getForObject請求接口 使用HashMap傳入參數
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("username", "laowan");
String result2 = template.getForObject(url, String.class, paramMap);
System.out.println("result2====================" + result1);
//三、使用url路徑變量PathVariable
String result3 = template.getForObject(url2, String.class);
System.out.println("result3====================" + result1);
ResponseEntity<String> responseEntity = template.getForEntity(url2, String.class);
System.out.println("getForEntity請求====================" + responseEntity.getBody());
//四、使用exchange請求接口
ResponseEntity<String> response2 = template.exchange(url, HttpMethod.GET, null, String.class,paramMap);
System.out.println("result4====================" + response2.getBody());
//五、使用exchange請求接口,能夠封裝HttpEntity對象傳遞header參數
HttpHeaders headers = new HttpHeaders();
headers.set("username", "laowan");
HttpEntity httpEntity = new HttpEntity(null,headers);
ResponseEntity<String> response5 = template.exchange(url, HttpMethod.GET, httpEntity, String.class,paramMap);
System.out.println("result5====================" + response5.getHeaders());
}
注意事項
get請求的參數傳遞,必定要在url後面拼接,可使用?號後面拼接,也能夠採用路徑參數。
傳遞參數有2中方法,一種是傳入多個參數值,會按順序填充到url後面的參數佔位符中,一種是採用map傳入多個參數,這時必定要使用HashMap
get請求若是須要傳遞header參數,必定要採用exchange方法,封裝HttpEntity對象
post請求
請求接口
@PostMapping("/user")
public User addUser(String username,Integer age){
User user = new User();
user.setAge(age);
user.setUsername(username);
log.info("新增用戶{}", JSON.toJSONString(user));
return user;
}
@PostMapping("/user/add")
public User addUser(User user){
log.info("新增用戶{}", JSON.toJSONString(user));
return user;
}
@PostMapping("/user/json")
public User addUserJson(@RequestBody User user){
log.info("新增用戶{}", JSON.toJSONString(user));
return user;
}
post請求
@Test
public void postRequestTest() {
String url = "http://127.0.0.1:8080/user";
String url2 = "http://127.0.0.1:8080/user/add";
RestTemplate template = new RestTemplate();
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();
paramMap.add("username", "laowan");
paramMap.add("age", 22);
User user = template.postForObject(url, paramMap, User.class);
log.info("result1的結果爲:{}",user);
user = template.postForObject(url2, paramMap, User.class);
log.info("result2的結果爲:{}",user);
}
//postForEntity方式的json請求
@Test
public void postRequestTest4() {
String url2 = "http://127.0.0.1:8080/user/json";
RestTemplate restTemplate = new RestTemplate();
String reqJsonStr = "{\"username\":\"laowan\", \"age\":\"12\"}";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<String>(reqJsonStr,headers);
ResponseEntity<User> resp = restTemplate.postForEntity(url2,entity, User.class);
log.info("result1的結果爲:{}",resp.getBody());
}
//經過exchange調用post請求,傳遞json格式參數
@Test
public void postRequestTest2() {
String url2 = "http://127.0.0.1:8080/user/json";
RestTemplate restTemplate = new RestTemplate();
String reqJsonStr = "{\"username\":\"laowan\", \"age\":\"12\"}";
HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<String>(reqJsonStr,headers);
ResponseEntity<User> resp = restTemplate.exchange(url2, HttpMethod.POST, entity, User.class);
log.info("result1的結果爲:{}",resp.getBody());
}
//經過exchange調用post請求,傳遞x-www-form-urlencoded格式參數
@Test
public void postRequestTest3() {
String url2 = "http://127.0.0.1:8080/user/add";
RestTemplate restTemplate = new RestTemplate();
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();
paramMap.add("username", "laowan");
paramMap.add("age", 22);
HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<MultiValueMap<String, Object>>(paramMap,headers);
ResponseEntity<User> resp = restTemplate.exchange(url2, HttpMethod.POST, entity, User.class);
if( resp.getStatusCode().equals( HttpStatus.OK )){
log.info("result1的結果爲:{}",resp.getBody());
}else{
throw new RuntimeException( resp.toString() );
}
}
注意事項
post請求傳遞普通參數,必定要使用LinkedMultiValueMap對參數進行封裝,否則會接收不到參數值。
post請求能夠傳遞json格式參數,須要在頭部參數中指定headers.setContentType(MediaType.APPLICATION_JSON);
返回ResponseEntity結果,主要是能夠從中獲取返回的狀態碼和請求頭信息。能夠方便對請求結果進行斷定,對請求異常進行處理。
if( resp.getStatusCode().equals( HttpStatus.OK )){
log.info("result1的結果爲:{}",resp.getBody());
}else{
throw new RuntimeException( resp.toString() );
}
結合ribbon使用
RestTemplate負載均衡示例
核心是經過@LoadBalanced註解聲明RestTemplate實例,主要邏輯是給RestTemplate增長攔截器,在請求以前對請求的地址進行替換,或者根據具體的負載均衡策略選擇服務地址,而後再去調用,這就是@LoadBalanced的原理。
引入jar包依賴
也能夠不引用,eureka中已經引用了ribbon
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
將服務rest註冊到Eureka
spring.application.name=rest
使用@LoadBalanced註解聲明RestTemplate實例
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
調用接口
修改調用的url,將IP+PORT改成服務名稱,也就是註冊到Eureka的名稱。
@SpringBootTest
class RestApplicationTests {
@Autowired
RestTemplate restTemplate;
@Test
void restRibbonTest() {
String result3 = restTemplate.getForObject("http://rest/users/laowan", String.class);
}
}
設置底層鏈接方式
RestTemplate默認依賴JDK提供http鏈接的能力(HttpURLConnection),若是有須要的話也能夠經過setRequestFactory方法替換爲例如 Apache HttpComponents、Netty或OkHttp等其它HTTP library。
實現原理是經過RestTemplate(ClientHttpRequestFactory requestFactory)的構造方法,指定requestFactory。
常規配置
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(RestTemplateBuilder restTemplateBuilder){
return restTemplateBuilder
.basicAuthentication("username", "password")
.setConnectTimeout(Duration.ofSeconds(3000))
.setReadTimeout(Duration.ofSeconds(5000))
.rootUri("http://api.example.com/")
.build();
// return new RestTemplate();
}
改用httpclient的實現
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.4.1</version>
</dependency>
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
// restTemplate.setRequestFactory(okHttpClient());
restTemplate.setRequestFactory(clientHttpRequestFactory());
//restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
return restTemplate;
}
@Bean
public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() {
try {
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
return true;
}
}).build();
httpClientBuilder.setSslcontext(sslContext);
HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
// 註冊http和https請求
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslConnectionSocketFactory).build();
// 開始設置鏈接池
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
poolingHttpClientConnectionManager.setMaxTotal(500); // 最大鏈接數500
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(100); // 同路由併發數100
httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)); // 重試次數
HttpClient httpClient = httpClientBuilder.build();
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); // httpClient鏈接配置
clientHttpRequestFactory.setConnectTimeout(20000); // 鏈接超時
clientHttpRequestFactory.setReadTimeout(30000); // 數據讀取超時時間
clientHttpRequestFactory.setConnectionRequestTimeout(20000); // 鏈接不夠用的等待時間
return clientHttpRequestFactory;
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
// log.error("初始化HTTP鏈接池出錯", e);
}
return null;
}
}
改用okhttp的實現
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.11.0</version>
</dependency>
@Configuration
public class OkHttpConfig {
@Bean
public OkHttpClient okHttpClient() {
return new OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory(), x509TrustManager())
.retryOnConnectionFailure(false)
.connectionPool(pool())
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30,TimeUnit.SECONDS)
.build();
}
@Bean
public X509TrustManager x509TrustManager() {
return new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
}
@Bean
public SSLSocketFactory sslSocketFactory() {
try {
//信任任何連接
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{x509TrustManager()}, new SecureRandom());
return sslContext.getSocketFactory();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
return null;
}
@Bean
public ConnectionPool pool() {
return new ConnectionPool(200, 5, TimeUnit.MINUTES);
}
}
能夠發現,在使用httpclient和okhttp均可以配置鏈接池connectionPool,相信可以在必定程度上提升http請求的速度。
手動指定轉換器
調用reseful接口傳遞的數據內容是json格式的字符串,返回的響應也是json格式的字符串。然而restTemplate.postForObject方法的請求參數RequestBean和返回參數ResponseBean卻都是java類。
是RestTemplate經過HttpMessageConverter自動幫咱們作了轉換的操做。
默認狀況下RestTemplate自動幫咱們註冊了一組HttpMessageConverter用來處理一些不一樣的contentType的請求。
如StringHttpMessageConverter來處理text/plain;
MappingJackson2HttpMessageConverter來處理application/json;
MappingJackson2XmlHttpMessageConverter來處理application/xml。
RestTemplate的無參構造方法中的轉化器配置部分源碼:
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
if (romePresent) {
this.messageConverters.add(new AtomFeedHttpMessageConverter());
this.messageConverters.add(new RssChannelHttpMessageConverter());
}
if (jackson2XmlPresent) {
this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
} else if (jaxb2Present) {
this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
if (jackson2Present) {
this.messageConverters.add(new MappingJackson2HttpMessageConverter());
} else if (gsonPresent) {
this.messageConverters.add(new GsonHttpMessageConverter());
} else if (jsonbPresent) {
this.messageConverters.add(new JsonbHttpMessageConverter());
}
你能夠在org.springframework.http.converter包下找到全部spring幫咱們實現好的轉換器。
若是現有的轉換器不能知足你的需求,你還能夠實現org.springframework.http.converter.HttpMessageConverter接口本身寫一個。詳情參考官方api。
選好了HttpMessageConverter後怎麼把它註冊到咱們的RestTemplate中呢。
RestTemplate restTemplate = new RestTemplate();
//獲取RestTemplate默認配置好的全部轉換器
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
//默認的MappingJackson2HttpMessageConverter在第7個 先把它移除掉
messageConverters.remove(6);
//添加上GSON的轉換器
messageConverters.add(6, new GsonHttpMessageConverter());
這個簡單的例子展現瞭如何使用GsonHttpMessageConverter替換掉默認用來處理application/json的MappingJackson2HttpMessageConverter。
總結
一、RestTemplate的常規調用方法和注意事項
二、RestTemplate結合Ribbon的負載均衡調用
三、RestTemplate底層鏈接的設置
四、RestTemplate的轉換器設置
參考:
https://blog.csdn.net/u014692324/article/details/98876094
https://blog.csdn.net/LDY1016/article/details/80002126
更多精彩,關注我吧。
本文分享自微信公衆號 - 跟着老萬學java(douzhe_2019)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。