一、概述
文章参考Spring之RestTemplate详解
1.1介绍
现如今的 IT 项目,由服务端向外发起网络请求的场景,基本上处处可见!
传统情况下,在服务端代码里访问 http 服务时,一般会使用 JDK 的 HttpURLConnection 或者 Apache 的 HttpClient,不过这种方法使用起来太过繁琐,而且 api 使用起来非常的复杂,还得操心资源回收。
以下载文件为例,通过 Apache 的 HttpClient方式进行下载文件,会很复杂
其实Spring已经为我们提供了一种简单便捷的模板类来进行操作,它就是RestTemplate
1.2‘什么是RestTemplate?
- RestTemplate是Spring提供的进行远程调用客户端
- RestTemplate提供了很多远程调用的方法,能够大大提高客户端的编写效率。
调用RestTemplate的默认构造函数,RestTemplate对象在底层通过使用java.net包下的实现创建HTTP 请求 - 官网地址RestTemplate (Spring Framework 6.0.11 API)
使用 使用restTemplate访问restful接口非常的简单粗暴无脑。 (url, requestMap, ResponseBean.class)这三个参数分别代表 REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。
1.3配置
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
同时,将RestTemplate
配置初始化为一个Bean
@Configuration
public class RestTemplateConfig
{@Bean@LoadBalancedpublic RestTemplate restTemplate(){return new RestTemplate();}
}
@LoadBalanced
@LoadBalanced
注解是Spring Cloud中的一个注解,用于在RestTemplate上启用客户端负载均衡。当使用Spring Cloud和Netflix Eureka等服务发现和注册中心时,可以使用@LoadBalanced
注解让RestTemplate自动从服务注册表中选择一个可用的服务实例进行调用。
注意,这种初始化方法,是使用了JDK
自带的HttpURLConnection
作为底层HTTP
客户端实现。
ClientHttpRequestFactory接口主要提供了三种实现方式
- 一种是SimpleClientHttpRequestFactory,使用J2SE提供的方式(既java.net包提供的方式)创建底层的Http请求连接
- 一种方式是使用HttpComponentsClientHttpRequestFactory方式,底层使用HttpClient访问远程的Http服务,使用HttpClient可以配置连接池和证书等信息
- 第三种方式是使用OkHttp3ClientHttpRequestFactory方式,底层使用OkHttp访问远程的Http服务,使用HttpClient可以配置连接池和证书等信息
- RestTemplate默认是使用SimpleClientHttpRequestFactory,内部是调用jdk的HttpConnection,默认超时为-1
优点:连接池、超时时间设置、支持异步、请求和响应的编解码
缺点:依赖别的spring版块、参数传递不灵活
1.4缺点与新技术
org.springframework.web.client public class RestTemplate
extends InterceptingHttpAccessor
implements RestOperations
同步客户端执行HTTP请求,在底层HTTP客户端库(如JDK HttpURLConnection、Apache HttpComponents等)上公开一个简单的模板方法API。RestTemplate通过HTTP方法为常见场景提供了模板,此外还提供了支持不太常见情况的通用交换和执行方法。 RestTemplate通常用作共享组件。然而,它的配置不支持并发修改,因此它的配置通常是在启动时准备的。如果需要,您可以在启动时创建多个不同配置的RestTemplate实例。如果这些实例需要共享HTTP客户端资源,它们可以使用相同的底层ClientHttpRequestFactory。 注意:从5.0开始,这个类处于维护模式,只有对更改和错误的小请求才会被接受。请考虑使用org.springframework.web.react .client. webclient,它有更现代的API,支持同步、异步和流场景。
二、API 实践
RestTemplate
最大的特色就是对各种网络请求方式做了包装,能极大的简化开发人员的工作量,下面我们以GET、POST、PUT、DELETE、文件上传与下载
为例,分别介绍各个API的使用方式
2.1GET请求
通过RestTemplate
发送HTTP GET
协议请求,经常使用到的方法有两个:
getForObject()
:返回值是HTTP
协议的响应体getForEntity()
:返回的是ResponseEntity
,ResponseEntity
是对HTTP
响应的封装,除了包含响应体,还包含HTTP
状态码、contentType、contentLength、Header
等信息
在Spring Boot
环境下写一个单元测试用例,首先创建一个Api
接口,然后编写单元测试进行服务测试。
不带参请求
不带参的get
请求
@RestController
public class TestController {/*** 不带参的get请求* @return*/@RequestMapping(value = "testGet", method = RequestMethod.GET)public ResponseBean testGet(){ResponseBean result = new ResponseBean();result.setCode("200");result.setMsg("请求成功,方法:testGet");return result;}
}
public class ResponseBean {private String code;private String msg;省去getset方法
}
@Autowired
private RestTemplate restTemplate;
/*** 单元测试(不带参的get请求)*/
@Test
public void testGet(){//请求地址String url = "http://localhost:8080/testGet";//发起请求,直接返回对象ResponseBean responseBean = restTemplate.getForObject(url, ResponseBean.class);System.out.println(responseBean.toString());
}
带参的get请求(使用占位符号传参)
@RestController
public class TestController {/*** 带参的get请求(restful风格)* @return*/@RequestMapping(value = "testGetByRestFul/{id}/{name}", method = RequestMethod.GET)public ResponseBean testGetByRestFul(@PathVariable(value = "id") String id, @PathVariable(value = "name") String name){ResponseBean result = new ResponseBean();result.setCode("200");result.setMsg("请求成功,方法:testGetByRestFul,请求参数id:" + id + "请求参数name:" + name);return result;}
}
@Autowired
private RestTemplate restTemplate;/*** 单元测试(带参的get请求)*/
@Test
public void testGetByRestFul(){//请求地址String url = "http://localhost:8080/testGetByRestFul/{1}/{2}";//发起请求,直接返回对象(restful风格)ResponseBean responseBean = restTemplate.getForObject(url, ResponseBean.class, "001", "张三");System.out.println(responseBean.toString());
}
带参的get请求(restful风格)
@RestController
public class TestController {/*** 带参的get请求(使用占位符号传参)* @return*/@RequestMapping(value = "testGetByParam", method = RequestMethod.GET)public ResponseBean testGetByParam(@RequestParam("userName") String userName,@RequestParam("userPwd") String userPwd){ResponseBean result = new ResponseBean();result.setCode("200");result.setMsg("请求成功,方法:testGetByParam,请求参数userName:" + userName + ",userPwd:" + userPwd);return result;}
}
@Autowired
private RestTemplate restTemplate;/*** 单元测试(带参的get请求)*/
@Test
public void testGetByParam(){//请求地址String url = "http://localhost:8080/testGetByParam?userName={userName}&userPwd={userPwd}";//请求参数Map<String, String> uriVariables = new HashMap<>();uriVariables.put("userName", "唐三藏");uriVariables.put("userPwd", "123456");//发起请求,直接返回对象(带参数请求)ResponseBean responseBean = restTemplate.getForObject(url, ResponseBean.class, uriVariables);System.out.println(responseBean.toString());
}
getForEntity使用示例
上面的所有的getForObject请求传参方法,getForEntity都可以使用,使用方法上也几乎是一致的,只是在返回结果接收的时候略有差别。
使用ResponseEntity<T> responseEntity来接收响应结果。用responseEntity.getBody()获取响应体。
/*** 单元测试*/
@Test
public void testAllGet(){//请求地址String url = "http://localhost:8080/testGet";//发起请求,返回全部信息ResponseEntity<ResponseBean> response = restTemplate.getForEntity(url, ResponseBean.class);// 获取响应体System.out.println("HTTP 响应body:" + response.getBody().toString());// 以下是getForEntity比getForObject多出来的内容HttpStatus statusCode = response.getStatusCode();int statusCodeValue = response.getStatusCodeValue();HttpHeaders headers = response.getHeaders();System.out.println("HTTP 响应状态:" + statusCode);System.out.println("HTTP 响应状态码:" + statusCodeValue);System.out.println("HTTP Headers信息:" + headers);
}
header设置参数
//请求头
HttpHeaders headers = new HttpHeaders();
headers.add("token", "123456789");//封装请求头
HttpEntity<MultiValueMap<String, Object>> formEntity = new HttpEntity<>(headers);ResponseEntity<Map> exchange = restTemplate.exchange('请求的url', HttpMethod.GET, formEntity, Map.class);
2.2POST请求
其实POST
请求方法和GET
请求方法上大同小异,RestTemplate
的POST
请求也包含两个主要方法:
postForObject()
:返回body
对象postForEntity()
:返回全部的信息
模拟表单请求
模拟表单请求,post
方法测试
@RestController
public class TestController {/*** 模拟表单请求,post方法测试* @return*/@RequestMapping(value = "testPostByForm", method = RequestMethod.POST)public ResponseBean testPostByForm(@RequestParam("userName") String userName,@RequestParam("userPwd") String userPwd){ResponseBean result = new ResponseBean();result.setCode("200");result.setMsg("请求成功,方法:testPostByForm,请求参数userName:" + userName + ",userPwd:" + userPwd);return result;}
}
@Autowired
private RestTemplate restTemplate;/*** 模拟表单提交,post请求*/
@Test
public void testPostByForm(){//请求地址String url = "http://localhost:8080/testPostByForm";// 请求头设置,x-www-form-urlencoded格式的数据HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);//提交参数设置MultiValueMap<String, String> map = new LinkedMultiValueMap<>();map.add("userName", "唐三藏");map.add("userPwd", "123456");// 组装请求体HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);//发起请求ResponseBean responseBean = restTemplate.postForObject(url, request, ResponseBean.class);System.out.println(responseBean.toString());
}
模拟表单请求(传递对象)
模拟表单请求,post
方法测试(对象接受)
@RestController
public class TestController {/*** 模拟表单请求,post方法测试* @param request* @return*/@RequestMapping(value = "testPostByFormAndObj", method = RequestMethod.POST)public ResponseBean testPostByForm(RequestBean request){ResponseBean result = new ResponseBean();result.setCode("200");result.setMsg("请求成功,方法:testPostByFormAndObj,请求参数:" + JSON.toJSONString(request));return result;}
}public class RequestBean {private String userName;private String userPwd;省去getset方法
}
@Autowired
private RestTemplate restTemplate;/*** 模拟表单提交,post请求*/
@Test
public void testPostByForm(){//请求地址String url = "http://localhost:8080/testPostByFormAndObj";// 请求头设置,x-www-form-urlencoded格式的数据HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);//提交参数设置MultiValueMap<String, String> map = new LinkedMultiValueMap<>();map.add("userName", "唐三藏");map.add("userPwd", "123456");// 组装请求体HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);//发起请求ResponseBean responseBean = restTemplate.postForObject(url, request, ResponseBean.class);System.out.println(responseBean.toString());
}
模拟JSON请求
模拟JSON
请求,post
方法测试
@RestController
public class TestController {/*** 模拟JSON请求,post方法测试* @param request* @return*/@RequestMapping(value = "testPostByJson", method = RequestMethod.POST)public ResponseBean testPostByJson(@RequestBody RequestBean request){ResponseBean result = new ResponseBean();result.setCode("200");result.setMsg("请求成功,方法:testPostByJson,请求参数:" + JSON.toJSONString(request));return result;}
}
@Autowired
private RestTemplate restTemplate;
/*** 模拟JSON提交,post请求*/
@Test
public void testPostByJson(){//请求地址String url = "http://localhost:8080/testPostByJson";//入参RequestBean request = new RequestBean();request.setUserName("唐三藏");request.setUserPwd("123456789");//发送post请求,并打印结果,以String类型接收响应结果JSON字符串ResponseBean responseBean = restTemplate.postForObject(url, request, ResponseBean.class);System.out.println(responseBean.toString());
}
模拟页面重定向
模拟页面重定向,post
请求
@Controller
public class LoginController {/*** 重定向* @param request* @return*/@RequestMapping(value = "testPostByLocation", method = RequestMethod.POST)public String testPostByLocation(@RequestBody RequestBean request){return "redirect:index.html";}
}
@Autowired
private RestTemplate restTemplate;
/*** 重定向,post请求*/
@Test
public void testPostByLocation(){//请求地址String url = "http://localhost:8080/testPostByLocation";//入参RequestBean request = new RequestBean();request.setUserName("唐三藏");request.setUserPwd("123456789");//用于提交完成数据之后的页面跳转,返回跳转urlURI uri = restTemplate.postForLocation(url, request);System.out.println(uri.toString());
}输出结果如下:
http://localhost:8080/index.html
2.3PUT请求
put
请求方法,可能很多人都没用过,它指的是修改一个已经存在的资源或者插入资源,该方法会向URL
代表的资源发送一个HTTP PUT
方法请求,示例如下
@RestController
public class TestController {/*** 模拟JSON请求,put方法测试* @param request* @return*/@RequestMapping(value = "testPutByJson", method = RequestMethod.PUT)public void testPutByJson(@RequestBody RequestBean request){System.out.println("请求成功,方法:testPutByJson,请求参数:" + JSON.toJSONString(request));}
}
@Autowired
private RestTemplate restTemplate;
/*** 模拟JSON提交,put请求*/
@Test
public void testPutByJson(){//请求地址String url = "http://localhost:8080/testPutByJson";//入参RequestBean request = new RequestBean();request.setUserName("唐三藏");request.setUserPwd("123456789");//模拟JSON提交,put请求restTemplate.put(url, request);
}
2.4DELETE请求
与之对应的还有delete
方法协议,表示删除一个已经存在的资源,该方法会向URL
代表的资源发送一个HTTP DELETE
方法请求。
@RestController
public class TestController {/*** 模拟JSON请求,delete方法测试* @return*/@RequestMapping(value = "testDeleteByJson", method = RequestMethod.DELETE)public void testDeleteByJson(){System.out.println("请求成功,方法:testDeleteByJson");}
}
@Autowired
private RestTemplate restTemplate;
/*** 模拟JSON提交,delete请求*/
@Test
public void testDeleteByJson(){//请求地址String url = "http://localhost:8080/testDeleteByJson";//模拟JSON提交,delete请求restTemplate.delete(url);
}
2.5 通用请求方法exchange方法
如果以上方法还不满足你的要求。在RestTemplate
工具类里面,还有一个exchange
通用协议请求方法,它可以发送GET、POST、DELETE、PUT、OPTIONS、PATCH等等HTTP
方法请求
2.6通过服务名调用
简介
在Spring Cloud
中,通过服务名称访问其他服务通常需要使用@LoadBalanced
的RestTemplate
,可以使用服务名(service ID
)来代替具体的URL
。
@Configuration
public class Config
{@LoadBalanced@Beanpublic RestTemplate restTemplate() {SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();// 设置超时requestFactory.setConnectTimeout(60 * 1000);requestFactory.setReadTimeout(60 * 1000);//利用复杂构造器可以实现超时设置,内部实际实现为 HttpClientRestTemplate restTemplate = new RestTemplate(requestFactory);return restTemplate ;}
}
上面的代码定义了一个 RestTemplate bean,并使用@LoadBalanced注解来开启负载均衡。这样,就可以在 应用中自动注入RestTemplate,然后使用服务名称来发送请求,即可通过 http://SERVICE-NAME/quest-path 访问对应名称的微服务
注意
:这种方法需要服务被注册到服务发现组件(如Eureka
,Nacos
,Consul
等)。这样,应用才能找到服务名对应的实际网络位置。
@LoadBalanced作用
@LoadBalanced
是 Netflix
的 ribbon
中的一个负载均衡的注解,并完成以下工作:
- 从负载均衡器中选一个对应的服务实例,所有的服务名实例都放在负载均衡器中的serverlist中;
- 从挑选的实例中去请求内容;
- 由服务名转为真正使用的ip地址;