restTemplate的介绍和使用

红太狼 2023-09-30 09:49 86阅读 0赞

大家好,我是修真院成都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

866aaf3b0cbbe1a56a0530557d5c3841.png

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 来实现。

765e23fe758f7471769dc0b863f746bd.png

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)”

);

}

af5cfebe9b461a48b133fbcb4b02fa35.png

SimpleClientHttpRequestFactory — 4.3.2 版本的代码默认

delete connection.setDoOutput = fase

DoOutput 的属性作用是可以使用 conn.getOutputStream().write() ,这样就能发送请求体了

a5835f089c49f516322def4c740d8fb2.png

6)接着执行 requestCallback.doWithRequest(request);

RequestCallback 封装了请求体和请求头对象,也就是说在该对象里面可以拿到我们需要的请求参数,在执行 doWithRequest 时,有一个非常重要的步骤,他和前面Connection发送请求体有着密切关系,我们知道请求头就是 SimpleBufferingClientHttpRequest.addHeaders 方法,那么请求体 bufferedOutput 是如何赋值的呢?就是在 doWithRequest 里面,如下 StringHttpMessageConverter (其他 MessageConvert 也一样,这里也是经常乱码的原因)

829c417627f6824649d84ebb1d433823.png

其中 s 就是请求体,HttpOutputMessage 对象就是我们准备的 ClientHttpRequest 对象,也就是上面的 SimpleBufferingClientHttpRequest extends AbstractBufferingClientHttpRequest

2643df660162935c0250a3bdc9573caa.png

这样,先调用父类的流方法,把内容写入流中,然后调用父类的 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

20520adc7444b38c7f44e68f4e645768.png

SimpleBufferingClientHttpRequest — 4.3.2 版本的代码默认

delete 时通过请求方式和是否有请求体对象来判断是否需要发送请求体

如果是delete请求,首先设置 DoOutput = true,然后根据是否有请求体数据,然后封装请求体

FileCopyUtils.copy(bufferedOutput, this.connection.getOutputStream());

c7ec7bef48dec5d1802381ac0acd2ea2.png

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、请求参数的传递

2d7bdc7324cc30168a53ca501da9cfd4.png

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 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、编码实战

配置缓存管理器

  1. @Configuration//相当于beans标签
  2. @EnableCaching//注解驱动的缓存管理器
  3. public class RedisConfiguration extends CachingConfigurerSupport {
  4. /* @Autowired
  5. private RedisConnectionFactory connectionFactory;*/
  6. /**
  7. * @Description: 指定redis主键生成规则:包名+方法名+参数名列表(原有:参数组合)
  8. * @return: org.springframework.cache.interceptor.KeyGenerator
  9. * @Date: 2018/6/28 17:11
  10. */
  11. /* @Bean
  12. @Override
  13. public KeyGenerator keyGenerator() {
  14. return new KeyGenerator() {
  15. @Override
  16. public Object generate(Object o, Method method, Object... objects) {
  17. StringBuffer stringBuffer = new StringBuffer();
  18. stringBuffer.append(o.getClass().getName());
  19. stringBuffer.append("::" + method.getName() + ":");
  20. for (Object object : objects)
  21. stringBuffer.append(object.toString());
  22. return stringBuffer.toString();
  23. }
  24. };
  25. }*/
  26. /**
  27. * @Description: 缓存管理器
  28. * @return: org.springframework.cache.CacheManager
  29. * @Date: 2018/6/28 17:12
  30. */
  31. @Bean
  32. public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
  33. //通过连接工厂初始化RedisCacheWriter
  34. RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
  35. ClassLoader loader = this.getClass().getClassLoader();
  36. ObjectMapper om = new ObjectMapper();
  37. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  38. om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  39. GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer =
  40. new GenericJackson2JsonRedisSerializer(om);
  41. RedisSerializationContext.SerializationPair<Object> rs =
  42. RedisSerializationContext.SerializationPair.fromSerializer(genericJackson2JsonRedisSerializer);
  43. //设置value序列化方式,如果自带value序列化方式是jdkSerializer,存在redis之中会添加一些东西,人还看不懂
  44. //GenericJackson2JsonRedisSerializer序列化方法存储的大小是jdkSerializer的五分之一,并且是人能够读懂的值
  45. RedisCacheConfiguration redisCacheConfiguration =
  46. RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(rs);
  47. //设置默认过期时间
  48. redisCacheConfiguration.entryTtl(Duration.ofDays(1));
  49. return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
  50. }
  51. /**
  52. * @Description: 设置RedisTemplate的序列化方式
  53. * @return: org.springframework.data.redis.core.RedisTemplate<java.lang.String , java.lang.Object>
  54. * @Date: 2018/6/28 17:13
  55. */
  56. @Bean(name = "redisTemplate")
  57. public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
  58. RedisTemplate<String, Object> template = new RedisTemplate<>();
  59. RedisSerializer<String> redisSerializer = new StringRedisSerializer();
  60. ObjectMapper om = new ObjectMapper();
  61. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  62. om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  63. GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer =
  64. new GenericJackson2JsonRedisSerializer(om);
  65. template.setConnectionFactory(factory);
  66. //key序列化方式
  67. template.setKeySerializer(redisSerializer);
  68. //value序列化
  69. template.setValueSerializer(genericJackson2JsonRedisSerializer);
  70. //value hashmap序列化
  71. template.setHashValueSerializer(genericJackson2JsonRedisSerializer);
  72. return template;
  73. }
  74. }

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、更多讨论

发表评论

表情:
评论列表 (有 0 条评论,86人围观)

还没有评论,来说两句吧...

相关阅读

    相关 restTemplate使用

    目录 目录 一、概述? 二、使用步骤 1.引入依赖 2.创建RestTemplate对象,交由spring容器进行管理 3.使用方法 3.1 GET请求