restTemplate的介绍和使用
大家好,我是修真院成都11期學員,今天為大家講一下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() {undefined
return this.requestFactory;
}
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {undefined
ClientHttpRequest request = getRequestFactory().createRequest(url, method);
if (logger.isDebugEnabled()) {undefined
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) {undefined
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 對象其實都是由工廠管理的,所以我們不關心連接池的管理,只是配置連接池初始化的一些參數而已。
這個可以參考:
Spring提供的用于訪問Rest服務的客戶端:RestTemplate實踐 - WEB服務/RPC/SOA - 軟件開發 - 深度開源
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 { /* @Autowiredprivate RedisConnectionFactory connectionFactory;*//*** @Description: 指定redis主鍵生成規則:包名+方法名+參數名列表(原有:參數組合)* @return: org.springframework.cache.interceptor.KeyGenerator* @Date: 2018/6/28 17:11*/ /* @Bean@Overridepublic KeyGenerator keyGenerator() {return new KeyGenerator() {@Overridepublic 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*/@Beanpublic CacheManager cacheManager(RedisConnectionFactory connectionFactory) {//通過連接工廠初始化RedisCacheWriterRedisCacheWriter 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、更多討論
總結
以上是生活随笔為你收集整理的restTemplate的介绍和使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 来来往往(说一说来来往往的简介)
- 下一篇: 遛鬼酥油饼百度云(遛鬼酥油饼)