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

猜你喜欢

转载自blog.csdn.net/qq_34468186/article/details/82893207