restTemplate的介紹和使用
1、背景介紹
Spring RestTemplate 是 Spring 提供的用於訪問 Rest 服務的客戶端,RestTemplate 提供了多種便捷訪問遠端Http服務的方法,能夠大大提高客戶端的編寫效率,所以很多客戶端比如 Android或者第三方服務商都是使用 RestTemplate 請求 restful 服務。
2、知識剖析
呼叫 RestTemplate 的預設建構函式,RestTemplate 物件在底層通過使用 java.net 包下的實現建立 HTTP 請求,可以通過使用 ClientHttpRequestFactory 指定不同的HTTP請求方式。預設使用 SimpleClientHttpRequestFactory,是 ClientHttpRequestFactory 實現類。如下流程:
1)使用預設構造方法new一個例項
RestTemplate template = new RestTemplate();
2)RestTemplate 內部通過呼叫 doExecute 方法,首先就是獲取 ClientHttpRequest
3)RestTemplate 實現了抽象類 HttpAccessor ,所以可以呼叫父類的 createRequest
private ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
public ClientHttpRequestFactory getRequestFactory() {
return this.requestFactory;
}
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
ClientHttpRequest request = getRequestFactory().createRequest(url, method);
if (logger.isDebugEnabled()) {
logger.debug("Created " + method.name() + " request for \"" + url + "\"");
}
return request;
}
4)SimpleClientHttpRequestFactory 實現了 ClientHttpRequest,同時實現方法
注意 bufferRequestBody 是可以在 RestTemplate 設定,是標誌是否使用快取流的形式,預設是 true,缺點是當傳送大量資料時,比如put/post的儲存和修改,那麼可能記憶體消耗嚴重。所以這時候可以設定 RestTemplate.setBufferRequestBody(false);
即使用 SimpleStreamingClientHttpRequest 來實現。
5)openConnection 沒什麼文章,而是 prepareConnection 則是大有文章,這裡我們分兩個版本來說,因為我們一開始使用 4.1.1 的時候不能使用帶請求體的delete,可是在 4.3.2 版本則可以使用,所以特別區分了這兩個版本的程式碼,如下:
SimpleClientHttpRequestFactory -- 4.1.1 版本的程式碼預設
delete connection.setDoOutput = fase
如果設定false,然後後面又去獲取輸出流時,會發生如下錯誤 sun 包的 HttpURLConnection
if(!this.doOutput) {
throw new ProtocolException(
"cannot write to a URLConnection if doOutput=false - call setDoOutput(true)"
);
}
SimpleClientHttpRequestFactory -- 4.3.2 版本的程式碼預設
delete connection.setDoOutput = fase
DoOutput 的屬性作用是可以使用 conn.getOutputStream().write() ,這樣就能傳送請求體了
6)接著執行 requestCallback.doWithRequest(request);
RequestCallback 封裝了請求體和請求頭物件,也就是說在該物件裡面可以拿到我們需要的請求引數,在執行 doWithRequest 時,有一個非常重要的步驟,他和前面Connection傳送請求體有著密切關係,我們知道請求頭就是 SimpleBufferingClientHttpRequest.addHeaders 方法,那麼請求體 bufferedOutput 是如何賦值的呢?就是在 doWithRequest 裡面,如下 StringHttpMessageConverter (其他 MessageConvert 也一樣,這裡也是經常亂碼的原因)
其中 s 就是請求體,HttpOutputMessage 物件就是我們準備的 ClientHttpRequest 物件,也就是上面的 SimpleBufferingClientHttpRequest extends AbstractBufferingClientHttpRequest
這樣,先呼叫父類的流方法,把內容寫入流中,然後呼叫父類的 executeInternal方法在呼叫自身的該方法 executeInternal ,如下一步
7)接著執行 response = request.execute();
然後使用例項 SimpleBufferingClientHttpRequest 封裝請求體和請求頭
SimpleBufferingClientHttpRequest -- 4.1.1 版本的程式碼預設
delete 時通過前面設定的 DoOutput 引數和是否可以設定輸出流來判斷是否需要傳送請求體
如果是 delete 請求,那麼很明顯 DoOutput = false,所以不會有封裝請求體的過程,即不執行
FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());
所以服務端無法獲取到請求體,會出現 HttpMessageNotReadableException: Required request body is missing
SimpleBufferingClientHttpRequest -- 4.3.2 版本的程式碼預設
delete 時通過請求方式和是否有請求體物件來判斷是否需要傳送請求體
如果是delete請求,首先設定 DoOutput = true,然後根據是否有請求體資料,然後封裝請求體
FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());
8)最後解析response
接著就是 response 的解析了,主要還是 Error 的解析。
handleResponseError(method, url, response);
3、RestTemplate 的配置項
1)setBufferRequestBody 是否是否緩衝流來儲存請求體,預設true
2)setProxy 設定代理物件
3)setChunkSize 設定每次傳輸位元組長度,與 setBufferRequestBody(false) 結合使用
4)setConnectTimeout 設定連線超時時間,預設 -1
5)setReadTimeout 設定讀取內容超時時間,預設 -1
6)setOutputStreaming 設定Connection是否設定輸出流程
7)setTaskExecutor 設定非同步回撥執行器
4、RestTemplate 設定 RequestFactory
其實任何有連線的地方都會有連線池的概念,比如資料庫連線等,這裡也不例外,肯定也會有,RestTemplate 預設有兩種工廠物件實現方式,都是 ClientHttpRequestFactory 的子類。如下
1)SimpleClientHttpRequestFactory 底層使用 java.net.HttpUrlConnection,可配置證書
2)HttpComponentsClientHttpRequestFactory 底層使用Apache HttpClient訪問遠端的Http服務,使用HttpClient同樣可以配置連線池和證書等資訊,而且功能更強大,配置項更多。
5、RequestFactory 的配置方式
1)使用XML配置,就是配置JavaBean
2)使用程式碼配置,就是初始化這個物件
無論上面那種方式配置,都是配置外殼 RestTemplate,真正傳送請求的 request 物件其實都是由工廠管理的,所以我們不關心連線池的管理,只是配置連線池初始化的一些引數而已。
這個可以參考:
http://www.open-open.com/lib/view/open1436018677419.html
6、請求引數的傳遞
7、關於網上說的無法傳送delete請求體
HttpMessageNotReadableException: Required request body is missing
Spring MVC 的 @RequestBody 只支援RestTemplate 的 POST 和 PUT
但是 RestTemplate 的 delete 方法並不支援傳入請求體(Request Body)。經測試,通過呼叫 RestTemplate 類的exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<ResponseResult> responseType, Object... uriVariables) 方法,將 method 指定為 org.springframework.http.HttpMethod.DELETE,並傳入 requestEntity(請求體) 物件時,在服務端得到的 Request Body 仍然為 null。可見 RestTemplate 預設並不支援對 DELETE 方法使用請求體。
通過查閱資料發現 RestTemplate 預設是使用 spring 自身的 SimpleClientHttpRequestFactory 建立請求物件和對其進行相關設定(如請求頭、請求體等),它只支援 PUT 和 POST 方法帶請求體,RestTemplate 的 DELETE 方法不支援傳入請求體是因為 JDK 中 HttpURLConnection 物件的 delete 方法不支援傳入請求體(如果對 HttpURLConnection 物件的 delete 方法傳入請求體,在執行時會丟擲 IOException)。
從程式碼中也看到了 Spring 對 delete 做了判斷,如果是 4.1.1 及以前的版本,確實是會出現上面的問題,但是當我使用 4.3.2 之後的版本,發現完全可以傳送請求體,這裡面的變化就是前者在程式碼中把請求體過濾掉了,後者把請求體加上了。至於更細的細節,希望有人能夠繼續深究下去。
3、常見問題
4、解決方案
5、編碼實戰
配置快取管理器
@Configuration//相當於beans標籤 @EnableCaching//註解驅動的快取管理器 public class RedisConfiguration extends CachingConfigurerSupport { /* @Autowired private RedisConnectionFactory connectionFactory;*/ /** * @Description: 指定redis主鍵生成規則:包名+方法名+引數名列表(原有:引數組合) * @return: org.springframework.cache.interceptor.KeyGenerator * @Date: 2018/6/28 17:11 */ /* @Bean @Override public KeyGenerator keyGenerator() { return new KeyGenerator() { @Override public Object generate(Object o, Method method, Object... objects) { StringBuffer stringBuffer = new StringBuffer(); stringBuffer.append(o.getClass().getName()); stringBuffer.append("::" + method.getName() + ":"); for (Object object : objects) stringBuffer.append(object.toString()); return stringBuffer.toString(); } }; }*/ /** * @Description: 快取管理器 * @return: org.springframework.cache.CacheManager * @Date: 2018/6/28 17:12 */ @Bean public CacheManager cacheManager(RedisConnectionFactory connectionFactory) { //通過連線工廠初始化RedisCacheWriter RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory); ClassLoader loader = this.getClass().getClassLoader(); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(om); RedisSerializationContext.SerializationPair<Object> rs = RedisSerializationContext.SerializationPair.fromSerializer(genericJackson2JsonRedisSerializer); //設定value序列化方式,如果自帶value序列化方式是jdkSerializer,存在redis之中會新增一些東西,人還看不懂 //GenericJackson2JsonRedisSerializer序列化方法儲存的大小是jdkSerializer的五分之一,並且是人能夠讀懂的值 RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(rs); //設定預設過期時間 redisCacheConfiguration.entryTtl(Duration.ofDays(1)); return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration); } /** * @Description: 設定RedisTemplate的序列化方式 * @return: org.springframework.data.redis.core.RedisTemplate<java.lang.String , java.lang.Object> * @Date: 2018/6/28 17:13 */ @Bean(name = "redisTemplate") public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); RedisSerializer<String> redisSerializer = new StringRedisSerializer(); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(om); template.setConnectionFactory(factory); //key序列化方式 template.setKeySerializer(redisSerializer); //value序列化 template.setValueSerializer(genericJackson2JsonRedisSerializer); //value hashmap序列化 template.setHashValueSerializer(genericJackson2JsonRedisSerializer); return template; } }
6、擴充套件思考
7、參考文獻
https://blog.csdn.net/guokezhongdeyuzhou/article/details/79789629
https://blog.csdn.net/u011851478/article/details/70239722
https://www.cnblogs.com/fashflying/p/6908028.html
8、更多討論
--------------------- 本文來自 weixin_41261521 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/weixin_41261521/article/details/81293265?utm_source=copy