2016-12-22   by 安静的下雪天  http://www.cnblogs.com/quiet-snowy-day/p/6210288.html

前言

在Web开发工作中,有一部分开发任务是不需要写web页面的。比如,本地服务在集成某些第三方的功能的时候(访问其他RESTful资源),通过转发URL请求到第三方服务,获取应答信息。这些应答信息不需要渲染到画面上,而是返回给客户端(APP或者其他web应用)。本地服务对于第三方服务来说是客户端;对于整体系统而言,就像是一个中转站。
这种开发内容除了业务逻辑,剩下的基本都是套路代码,而Spring从3.0版本开始,为我们提供了封装好的访问HTTP的模板代码RestTemplate。
 

RestTemplate 类说明

/**
* <strong>Spring's central class for synchronous client-side HTTP access.</strong>
* It simplifies communication with HTTP servers, and enforces RESTful principles.
* It handles HTTP connections, leaving application code to provide URLs
* (with possible template variables) and extract results.
*
* <p><strong>Note:</strong> by default the RestTemplate relies on standard JDK
* facilities to establish HTTP connections. You can switch to use a different
* HTTP library such as Apache HttpComponents, Netty, and OkHttp through the
* {@link #setRequestFactory} property.
*
* <p>The main entry points of this template are the methods named after the six main HTTP methods:
* <table>
* <tr><th>HTTP method</th><th>RestTemplate methods</th></tr>
* <tr><td>DELETE</td><td>{@link #delete}</td></tr>
* <tr><td>GET</td><td>{@link #getForObject}</td></tr>
* <tr><td></td><td>{@link #getForEntity}</td></tr>
* <tr><td>HEAD</td><td>{@link #headForHeaders}</td></tr>
* <tr><td>OPTIONS</td><td>{@link #optionsForAllow}</td></tr>
* <tr><td>POST</td><td>{@link #postForLocation}</td></tr>
* <tr><td></td><td>{@link #postForObject}</td></tr>
* <tr><td>PUT</td><td>{@link #put}</td></tr>
* <tr><td>any</td><td>{@link #exchange}</td></tr>
* <tr><td></td><td>{@link #execute}</td></tr> </table>
*
* <p>In addition the {@code exchange} and {@code execute} methods are generalized versions of
* the above methods and can be used to support additional, less frequent combinations (e.g.
* HTTP PATCH, HTTP PUT with response body, etc.). Note however that the underlying HTTP
* library used must also support the desired combination.
*
* <p>For each HTTP method there are three variants: two accept a URI template string
* and URI variables (array or map) while a third accepts a {@link URI}.
* Note that for URI templates it is assumed encoding is necessary, e.g.
* {@code restTemplate.getForObject("http://example.com/hotel list")} becomes
* {@code "http://example.com/hotel%20list"}. This also means if the URI template
* or URI variables are already encoded, double encoding will occur, e.g.
* {@code http://example.com/hotel%20list} becomes
* {@code http://example.com/hotel%2520list}). To avoid that use a {@code URI} method
* variant to provide (or re-use) a previously encoded URI. To prepare such an URI
* with full control over encoding, consider using
* {@link org.springframework.web.util.UriComponentsBuilder}.
*
* <p>Internally the template uses {@link HttpMessageConverter} instances to
* convert HTTP messages to and from POJOs. Converters for the main mime types
* are registered by default but you can also register additional converters
* via {@link #setMessageConverters}.
*
* <p>This template uses a
* {@link org.springframework.http.client.SimpleClientHttpRequestFactory} and a
* {@link DefaultResponseErrorHandler} as default strategies for creating HTTP
* connections or handling HTTP errors, respectively. These defaults can be overridden
* through {@link #setRequestFactory} and {@link #setErrorHandler} respectively.
*
* @author Arjen Poutsma
* @author Brian Clozel
* @author Roy Clarkson
* @author Juergen Hoeller
* @since 3.0
* @see HttpMessageConverter
* @see RequestCallback
* @see ResponseExtractor
* @see ResponseErrorHandler
* @see AsyncRestTemplate
*/

Java Doc

尝试翻译如下:
RestTemplate是 Spring中客户端同步访问HTTP的核心类。它简化了与HTTP服务器的通信,执行RESTful原则。
它能处理HTTP链接,委托应用程序代码(使用合适的模板变量)来装配URL,并提取应答信息。
 
注意:默认情况下,RestTemplate依赖标准JDK工具来创建HTTP链接。通过设置(HttpAccessor.setRequestFactory)属性,你可以转而使用像Apache HttpComponents、Netty、OkHttp这样的HTTP库。
 
该模板类的主要切入点为以下几个方法(并对应着HTTP的六个主要方法):
另外,exchange和execute方法提供了以上方法的通用版本,用来支持额外的、不常用的组合(如:HTTP PATCH,带有消息体的HTTP PUT,等等)。注意,无论怎样使用底层HTTP库,都必须支持必要的组合。
 
对于每个HTTP方法都有3个变体:
其中两个方法的接收参数是URI模式字符串和URI变量(array or map),第三个的接收参数是java.net.URI。
注意,需要为URI模式串假定编码格式,如:restTemplate.getForObject("http://example.com/hotel list") 变为 "http://example.com/hotel%20list"。
这同时也意味着,如果URI模式串或URI变量已经编码,会产生重复编码,如:"http://example.com/hotel%20list" 变成了 "http://example.com/hotel%2520list"。
为了避免使用URI方法变体来提供(或重用)预编码的URI,可以考虑使用UriComponentsBuilder,来制定可以完全控制编码的URI。
 
在模板内部使用HttpMessageConverter实例来实现HTTP消息与POJO类的相互转换。
主要的MIME类型的转换器已经默认注册,你也可以通过setMessageConverters(List<HttpMessageConverter<?>>)方法来注册额外的转换器。
 
本模板分别使用SimpleClientHttpRequestFactory 和 DefaultResponseErrorHandler作为默认策略,来创建HTTP链接、处理HTTP错误。通过HttpAccessor.setRequestFactory(ClientHttpRequestFactory) 和 setErrorHandler(ResponseErrorHandler)方法可以分别覆盖之前的默认设置。
 

补充说明:

重载的3个方法怎么选择呢?建议选择URL参数类型为String的那个两个方法。因为当这个参数是一个非URI格式的,需要进行转换,而URI的构造函数会抛出一个检查异常URISyntaxException,该异常必须捕获。另外两个重载方法则避免了捕获异常,所以上面表格中推荐的方法的第一个参数都是String类型。

简单例子

写了两个测试方法,博主喜欢用Junit ☺
package com.practice;

import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map; import junit.framework.TestCase;
import net.sf.json.JSONObject; import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate; public class RestTemplateTest extends TestCase { RestTemplate restTpl; @Before
public void setUp() {
restTpl = new RestTemplate();
} @Test
public void testGet() {
Map<String, Object> paramsMap = new HashMap<String, Object>();
paramsMap.put("cityCode", "xxxxxxxxxx");
paramsMap.put("key", "xxxxxxxxxxxxxxxxxxxxx"); String url = "http://xxx.xxx.xxx.xxx:8080/xxx/xxxx?xxxx&config=xxx&cityCode={cityCode}&key={key}";
String respStr = restTpl.getForObject(url, String.class, paramsMap);
System.out.println(respStr); JSONObject respJson = restTpl.getForObject(url, JSONObject.class, paramsMap);
System.out.println(respJson);
} @Test
public void testPost() throws Exception {
String posturl = "http://xxx.xxx.xxx.xxx:8080/xxxx/xxxx/xxxxx"; JSONObject metadata = new JSONObject();
metadata.put("dddddd", "xxxxx");
metadata.put("ssssss", "xxxxxx");
metadata.put("flag", true); JSONObject paramsJson = new JSONObject();
paramsJson.put("mmmmm", "mmm");
paramsJson.put("nnnnn", "nnn");
paramsJson.put("password", "xxxxxxxxxxx");
paramsJson.put("metadata", metadata); String params = "RequestJson=" + URLEncoder.encode(paramsJson.toString(), "utf-8"); MultiValueMap<String, String> headers = new LinkedMultiValueMap<String, String>();
headers.add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); HttpEntity<Object> hpEntity = new HttpEntity<Object>(params, headers); ResponseEntity<String> respEntity = restTpl.postForEntity(posturl, hpEntity, String.class);
System.out.println(respEntity);
} }

然而,就是这么简单的几行代码还不断出错~~~~~~~

之前postForEntity方法的第二个参数(请求消息体的类型)直接使用了JSONObject对象,发生了以下错误,
查看了spring-web源码,发现源码工程中引用的jackson-databind包的版本是2.8.1。改了jar包版本后,可以继续运行了。
 
然而,又错了,见下图。
这次是因为请求信息不完整,测试的服务端对POST消息体的格式有要求,而我没有设置Content-Type,请求头的信息只有Accept和Content-Length两项。
创建Map对象并设定头信息,改用HttpEntity作为发送对象,终于顺利执行了。

疑问解答

RestTemplate中是如何处理请求头信息的呢?

以GET方法为例,getForObject()方法中有这么一句【RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);】

其中 acceptHeaderRequestCallback 方法返回了 AcceptHeaderRequestCallback 类的实例。 
AcceptHeaderRequestCallback 是 RestTemplate 的内部类,实现了RequestCallback接口,该接口只有一个方法doWithRequest。
AcceptHeaderRequestCallback 实现了doWithRequest 方法:根据响应实体类型responseType,遍历所有的消息转换器,找到适合的,然后再从这些转换器中找到所有支持的媒体类型,最后将所有支持的媒体类型设置到请求头部Accept中。
再来看POST方法,postForEntity()方法中有【RequestCallback requestCallback = httpEntityCallback(request, responseType);】
其中 httpEntityCallback 方法返回了 HttpEntityRequestCallback 类的实例。
HttpEntityRequestCallback 也是RestTemplate 的内部类,它继承了 AcceptHeaderRequestCallback 类,重写了doWithRequest方法:首先调用父类doWithRequest方法,完成请求头部设定;然后,根据响应实体类型,通过遍历找到适合的消息转换器;最后通过消息转换器将POST消息体写入请求实体中。

这里为什么使用内部类呢?

复习下内部类的作用:

1. 隐藏了实现细节。AcceptHeaderRequestCallback 和 HttpEntityRequestCallback 的访问限定都是private;另外,在内部类中实现接口,而不是由 RestTemplate 类implement 多个接口,也就避免了对外部调用者暴露 doWithRequest 方法及其实现。
2. 内部类可以访问包含类的所有元素。在内部类的 doWithRequest 方法中访问了 RestTemplate 类的成员变量messageConverters,该变量是所有消息转换器的列表。
3. 多继承,这里是内部类的之间有继承关系,也算间接多继承?RestTemplate 类是继承了 InterceptingHttpAccessor 类,实现了 RestOperations 接口。
4. 避免因父类和实现接口中有重名的方法,而进行不必要的修改。这点到是没有明显体现,RestTemplate的继承类和两个接口中没有重名的方法。
 

请求和响应消息解析是怎么解析的?
是否用到某种设计模式?
RestTemplate发送的是同步请求,那么异步请求是如何处理呢?
感觉这个坑越挖越深,需要花点时间好好看看……
不用发愁博客没有内容可写了:P
 

【Spring-web】RestTemplate源码学习的更多相关文章

  1. spring源码学习(三)--spring循环引用源码学习

    在spring中,是支持单实例bean的循环引用(循环依赖)的,循环依赖,简单而言,就是A类中注入了B类,B类中注入了A类,首先贴出我的代码示例 @Component public class Add ...

  2. 捋一捋Spring Web的源码思路

    Servlet前提 Java规定了Servlet Container为每一个web app创建一个Servlet Context:而Servlet Context中又包含了诸多Servlet -- 其 ...

  3. 【Spring-web】RestTemplate源码学习——梳理内部实现过程

    2016-12-28 by 安静的下雪天  http://www.cnblogs.com/quiet-snowy-day/p/6228198.html  提示:使用手机浏览时请注意,图多费流量. 本篇 ...

  4. 【目录】Spring 源码学习

    [目录]Spring 源码学习 jwfy 关注 2018.01.31 19:57* 字数 896 阅读 152评论 0喜欢 9 用来记录自己学习spring源码的一些心得和体会以及相关功能的实现原理, ...

  5. spring源码学习——spring整体架构和设计理念

    Spring是在Rod Johnson的<Expert One-On-One J2EE Development and Design >的基础上衍生而来的.主要目的是通过使用基本的java ...

  6. Spring5.0源码学习系列之Spring AOP简述

    前言介绍 附录:Spring源码学习专栏 在前面章节的学习中,我们对Spring框架的IOC实现源码有了一定的了解,接着本文继续学习Springframework一个核心的技术点AOP技术. 在学习S ...

  7. Spring源码学习笔记12——总结篇,IOC,Bean的生命周期,三大扩展点

    Spring源码学习笔记12--总结篇,IOC,Bean的生命周期,三大扩展点 参考了Spring 官网文档 https://docs.spring.io/spring-framework/docs/ ...

  8. SpringBoot源码学习1——SpringBoot自动装配源码解析+Spring如何处理配置类的

    系列文章目录和关于我 一丶什么是SpringBoot自动装配 SpringBoot通过SPI的机制,在我们程序员引入一些starter之后,扫描外部引用 jar 包中的META-INF/spring. ...

  9. spring源码学习之路---深入AOP(终)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 上一章和各位一起看了一下sp ...

随机推荐

  1. ant的安装及项目的发布

    1.安装ant1) 直接解压apache-ant-1.9.7-bin 2) 在环境变量中配置,ant_home的环境变量在 3) 在命令提示符中测试是否安装成功. 2 项目首次打包1) 写好打包的配置 ...

  2. Python join()函数

    今天写python 100例时,有个题目是大致是这样的:已知输入形式是1+3+2+1,要求输出形式为1+1+2+3 一开始思路是将输入的字符串用split()函数划分成数组,在对数组进行排序,再用fo ...

  3. MaxTemperature程序Mapper ClassNotFoundException

    错误: 执行hadoop权威指南上MaxTemperature程序出现Mapper类ClassNotFoundException异常: 解决: 将书上的 JobConf job = new JobCo ...

  4. 初探ReactJS.NET 开发

    ReactJS通常也被称为"React",是一个刚刚在这场游戏中登场的新手.它由Facebook创建,并在2013年首次发布.Facebook认为React在处理SPA问题上可以成 ...

  5. 使用 SoapUI 测试ASP.NET Web API

    我们为不同的目的开发了很多web服务,经过授权的用户就可以访问和使用这些web服务.soapUI 是一个强大的测试web服务的工具,他不仅可以测试SOAP服务,他也支持测试RESTful服务.在这里我 ...

  6. Microsoft Avro介绍

    Microsoft发布了他们自己对Apache Avro通信协议的实现.Avro被描述为"紧凑的二进制数据序列化格式,类似于Thrift或者Protocol Buffers",同时 ...

  7. 一步步学习javascript基础篇(2):作用域和作用域链

    作用域和作用域链 js的语法用法非常的灵活,且稍不注意就踩坑.这集来分析下作用域和作用域链.我们且从几道题目入手,您可以试着在心里猜想着答案. 问题一. if (true) { var str = & ...

  8. 图解集合6:LinkedHashMap

    初识LinkedHashMap 上两篇文章讲了HashMap和HashMap在多线程下引发的问题,说明了,HashMap是一种非常常见.非常有用的集合,并且在多线程情况下使用不当会有线程安全问题. 大 ...

  9. ASP.Net MVC开发基础学习笔记:二、HtmlHelper与扩展方法

    一.一个功能强大的页面开发辅助类—HtmlHelper初步了解 1.1 有失必有得 在ASP.Net MVC中微软并没有提供类似服务器端控件那种开发方式,毕竟微软的MVC就是传统的请求处理响应的回归. ...

  10. 备忘录:hadoop技术一点积累

    1.hbase的rowkey是按字典排序的,我看有的资料建议rowkey设计不应该是自增的,应该和这个字典排序相关吧 2.hbase的数据存储是按照region来的,region的设计前段时间在坐飞机 ...