发送 http 请求,估计很多人用过 httpclient 和 okhttp,确实挺好用的,而 Spring web 中的 RestTemplate 和这俩的功能类似,也是用来发送 http 请求的,不过用法上面比前面的 2 位要容易很多。
spring 框架提供的 RestTemplate 类可用于在应用中调用 rest 服务,它简化了与 http 服务的通信方式,统一了 RESTful 的标准,封装了 http 链接, 我们只需要传入 url 及返回值类型即可。相较于之前常用的 HttpClient,RestTemplate 是一种更优雅的调用 RESTful 服务的方式。
在 Spring 应用程序中访问第三方 REST 服务与使用 Spring RestTemplate 类有关。RestTemplate 类的设计原则与许多其他 Spring 模板类(例如 JdbcTemplate、JmsTemplate)相同,为执行复杂任务提供了一种具有默认行为的简化方法。
RestTemplate 默认依赖 JDK 提供 http 连接的能力(HttpURLConnection),如果有需要的话也可以通过 setRequestFactory 方法替换为例如 Apache HttpComponents、Netty 或 OkHttp 等其它 HTTP library。
考虑到 RestTemplate 类是为调用 REST 服务而设计的,因此它的主要方法与 REST 的基础紧密相连就不足为奇了,后者是 HTTP 协议的方法:HEAD、GET、POST、PUT、DELETE 和 OPTIONS。例如,RestTemplate 类具有 headForHeaders()、getForObject()、postForObject()、put()和 delete()等方法。
发送 Get 请求 普通请求接口代码
@GetMapping("/test/get") @ResponseBody public BookDto get() { return new BookDto(1, "SpringMVC系列"); }
使用 RestTemplate 调用上面这个接口,通常有 2 种写法,如下
@Test public void test1() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/get"; //getForObject方法,获取响应体,将其转换为第二个参数指定的类型 BookDto bookDto = restTemplate.getForObject(url, BookDto.class); System.out.println(bookDto); } @Test public void test2() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/get"; //getForEntity方法,返回值为ResponseEntity类型 // ResponseEntity中包含了响应结果中的所有信息,比如头、状态、body ResponseEntityresponseEntity = restTemplate.getForEntity(url, BookDto.class); //状态码 System.out.println(responseEntity.getStatusCode()); //获取头 System.out.println("头:" + responseEntity.getHeaders()); //获取body BookDto bookDto = responseEntity.getBody(); System.out.println(bookDto); }
test1 输出
BookDto{id=1, name='SpringMVC系列'}
test2 输出
200 OK 头:[Content-Type:"application/json;charset=UTF-8", Transfer-Encoding:"chunked", Date:"Sat, 02 Oct 2021 07:05:15 GMT", Keep-Alive:"timeout=20", Connection:"keep-alive"] BookDto{id=1, name='SpringMVC系列'}url 中含有动态参数
url 中含有动态参数
@GetMapping("/test/get/{id}/{name}") @ResponseBody public BookDto get(@PathVariable("id") Integer id, @PathVariable("name") String name) { return new BookDto(id, name); }
使用 RestTemplate 调用上面这个接口,通常有 2 种写法,如下
@Test public void test3() { RestTemplate restTemplate = new RestTemplate(); //url中有动态参数 String url = "http://localhost:8080/chat16/test/get/{id}/{name}"; MapuriVariables = new HashMap<>(); uriVariables.put("id", "1"); uriVariables.put("name", "SpringMVC系列"); //使用getForObject或者getForEntity方法 BookDto bookDto = restTemplate.getForObject(url, BookDto.class, uriVariables); System.out.println(bookDto); } @Test public void test4() { RestTemplate restTemplate = new RestTemplate(); //url中有动态参数 String url = "http://localhost:8080/chat16/test/get/{id}/{name}"; Map uriVariables = new HashMap<>(); uriVariables.put("id", "1"); uriVariables.put("name", "SpringMVC系列"); //getForEntity方法 ResponseEntity responseEntity = restTemplate.getForEntity(url, BookDto.class, uriVariables); BookDto bookDto = responseEntity.getBody(); System.out.println(bookDto); }
test3 输出
BookDto{id=1, name='SpringMVC系列'}
test4 输出
BookDto{id=1, name='SpringMVC系列'}接口返回值为泛型
接口代码
@GetMapping("/test/getList") @ResponseBody public ListgetList() { return Arrays.asList( new BookDto(1, "Spring高手系列"), new BookDto(2, "SpringMVC系列") ); }
当接口的返回值为泛型的时候,这种情况比较特殊,使用 RestTemplate 调用上面这个接口,代码如下,需要用到restTemplate.exchange的方法,这个方法中有个参数是ParameterizedTypeReference类型,通过这个参数类指定泛型类型
@Test public void test5() { RestTemplate restTemplate = new RestTemplate(); //返回值为泛型 String url = "http://localhost:8080/chat16/test/getList"; //若返回结果是泛型类型的,需要使用到exchange方法, //这个方法中有个参数是ParameterizedTypeReference类型,通过这个参数类指定泛型类型 ResponseEntity> responseEntity = restTemplate.exchange(url, HttpMethod.GET, null, new ParameterizedTypeReference
>() { }); List
bookDtoList = responseEntity.getBody(); System.out.println(bookDtoList); }
输出
[BookDto{id=1, name='Spring高手系列'}, BookDto{id=2, name='SpringMVC系列'}]
PS:关于如何指定的实体类进行接收的时候, 比如上面只能使用exchange,其实我也比较喜欢这种,包含不同请求方式,但是这个方法指定返回值的类型有两种,XX.class和ParameterizedTypeReference,由于RestTemplate 默认的字符串转换器只支持JSONObject类型的,所以response返回值直接使用XX.class或者String指定都可以,只不过String指定的话,只需要进行json转换成自己想要的实体类即可
还有一种比如我想返回的想直接映射成这样的实体类DataPlatformResp,怎么办呢?
@Data public class DataPlatformResp{ private String code; private String msg; private int total; private List data; }
PS: 承上所述,关于这种统一返回格式的模型接收两种方式处理,①将上面data类型改为Object类型,这样就可以DataPlatformResp.class就行了,还有一种就是这样的
调用第三方接口代码:
RestTemplate restTemplate = new RestTemplate(); Mapmap = new HashMap(); map.put("XXX", XXX); HttpEntity> httpEntity = new HttpEntity<>(map, null); ResponseEntity responseEntity = null; try { responseEntity = restTemplate.exchange(URL, HttpMethod.POST, httpEntity, new ParameterizedTypeReference >() {}); } catch (Exception e) { e.printStackTrace(); throw new baseRuntimeException(new MutableCodeMessage("1", "第三方接口异常", ExceptionLevel.LEVEL_FATAL)); }
其中,返回体DataPlatformResp如下:
@Data public class DataPlatformResp{ private String code; private String msg; private int total; private List data; }
在执行之后,responseEntity返回的数据如下:
下载小文件PS:接着上面问题继续说,其实为什么会这样呢,关键就是默认的转换器不支持List《map》类型,所以为什么上面
@Data
public class DataPlatformResp {
private String code;
private String msg;
private int total;
private List《T》 data;
}
这样因为data是List类型的 ,如果是Object就可以了,其实想支持也行,添加自定义的json转换器
接口代码如下,这个接口会下载服务器端的 1.txt 文件。
@GetMapping("/test/downFile") @ResponseBody public HttpEntitydownFile() { //将文件流封装为InputStreamResource对象 InputStream inputStream = this.getClass().getResourceAsStream("/1.txt"); InputStreamResource inputStreamResource = new InputStreamResource(inputStream); //设置header MultiValueMap headers = new HttpHeaders(); headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=1.txt"); HttpEntity httpEntity = new HttpEntity<>(inputStreamResource); return httpEntity; }
使用 RestTemplate 调用这个接口,代码如下,目前这个文件的内容比较少,可以直接得到一个数组
@Test public void test6() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/downFile"; //文件比较小的情况,直接返回字节数组 ResponseEntityresponseEntity = restTemplate.getForEntity(url, byte[].class); //获取文件的内容 byte[] body = responseEntity.getBody(); String content = new String(body); System.out.println(content); }
注意:如果文件大的时候,这种方式就有问题了,会导致 oom,要用下面的方式了。
下载大文件接口代码,继续使用上面下载 1.txt 的代码
@GetMapping("/test/downFile") @ResponseBody public HttpEntitydownFile() { //将文件流封装为InputStreamResource对象 InputStream inputStream = this.getClass().getResourceAsStream("/1.txt"); InputStreamResource inputStreamResource = new InputStreamResource(inputStream); //设置header MultiValueMap headers = new HttpHeaders(); headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=1.txt"); HttpEntity httpEntity = new HttpEntity<>(inputStreamResource); return httpEntity; }
此时使用 RestTemplate 调用这个接口,代码如下
文件比较大的时候,比如好几个 G,就不能返回字节数组了,会把内存撑爆,导致 OOM,需要使用 execute 方法了,这个方法中有个 ResponseExtractor 类型的参数,restTemplate 拿到结果之后,会回调{@link ResponseExtractor#extractData}这个方法,在这个方法中可以拿到响应流,然后进行处理,这个过程就是变读边处理,不会导致内存溢出
@Test public void test7() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/downFile"; String result = restTemplate.execute(url, HttpMethod.GET, null, new ResponseExtractor传递头() { @Override public String extractData(ClientHttpResponse response) throws IOException { System.out.println("状态:"+response.getStatusCode()); System.out.println("头:"+response.getHeaders()); //获取响应体流 InputStream body = response.getBody(); //处理响应体流 String content = IOUtils.toString(body, "UTF-8"); return content; } }, new HashMap<>()); System.out.println(result); }
接口代码
@GetMapping("/test/header") @ResponseBody public Map> header(HttpServletRequest request) { Map > header = new linkedHashMap<>(); Enumeration headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); Enumeration values = request.getHeaders(name); List list = new ArrayList<>(); while (values.hasMoreElements()) { list.add(values.nextElement()); } header.put(name, list); } return header; }
使用 RestTemplate 调用接口,请求头中传递数据,代码如下,注意代码①和②,这两处是关键,用到了HttpHeaders和RequestEntity
请求头放在 HttpHeaders 对象中
RequestEntity:请求实体,请求的所有信息都可以放在 RequestEntity 中,比如 body 部分、头、请求方式、url 等信息
@Test public void test8() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/header"; //①:请求头放在HttpHeaders对象中 MultiValueMapheaders = new HttpHeaders(); headers.add("header-1", "V1"); headers.add("header-2", "Spring"); headers.add("header-2", "SpringBoot"); //②:RequestEntity:请求实体,请求的所有信息都可以放在RequestEntity中,比如body部分、头、请求方式、url等信息 RequestEntity requestEntity = new RequestEntity( null, //body部分数据 headers, //头 HttpMethod.GET,//请求方法 URI.create(url) //地址 ); ResponseEntity
输出
{accept=[application/json, application InputStream inputStream = RestTemplateTest.class.getResourceAsStream("/1.txt"); InputStreamResource inputStreamResource = new InputStreamResource(inputStream) { @Override public String getFilename() { return "1.txt"; } @Override public long contentLength() throws IOException { return inputStream.available(); } }; body.add("file1", inputStreamResource); //③:头 HttpHeaders headers = new HttpHeaders(); headers.add("header1", "v1"); headers.add("header2", "v2"); //④:请求实体 RequestEntity复杂表单:多个普通元素+多文件上传> requestEntity = new RequestEntity<>(body, headers, HttpMethod.POST, URI.create(url)); //⑤:发送请求(请求实体,返回值需要转换的类型) ResponseEntity > responseEntity = restTemplate.exchange( requestEntity, new ParameterizedTypeReference >() { }); Map result = responseEntity.getBody(); System.out.println(result); }
接口
@PostMapping("/test/form3") @ResponseBody public Mapform3(UserDto userDto) { Map result = new linkedHashMap<>(); result.put("name", userDto.getName()); result.put("headImg", userDto.getHeadImg().getOriginalFilename()); result.put("idImgList", Arrays.toString(userDto.getIdImgList().stream(). map(MultipartFile::getOriginalFilename).toArray())); return result; }
UserDto:包含了多个元素(姓名、头像、多张证件照),这种可以模拟复杂的表单
public class UserDto { //姓名 private String name; //头像 private MultipartFile headImg; //多张证件照 private ListidImgList; //get set 省略了... }
用 RestTemplate 调用这个接口,代码如下
@Test public void test14() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/form3"; //①:表单信息,需要放在MultiValueMap中,MultiValueMap相当于Map> MultiValueMap body = new linkedMultiValueMap<>(); body.add("name", "路人"); body.add("headImg", new FileSystemResource(".\src\main\resources\1.jpg")); //来2张证件照,元素名称一样 body.add("idImgList", new FileSystemResource(".\src\main\resources\2.jpg")); body.add("idImgList", new FileSystemResource(".\src\main\resources\3.jpg")); //③:头 HttpHeaders headers = new HttpHeaders(); headers.add("header1", "v1"); headers.add("header2", "v2"); //④:请求实体 RequestEntity > requestEntity = new RequestEntity<>(body, headers, HttpMethod.POST, URI.create(url)); //⑤:发送请求(请求实体,返回值需要转换的类型) ResponseEntity > responseEntity = restTemplate.exchange( requestEntity, new ParameterizedTypeReference >() { }); Map result = responseEntity.getBody(); System.out.println(result); }
输出
{name=路人, headImg=1.jpg, idImgList=[2.jpg, 3.jpg]}发送 json 格式数据:传递 java 对象
接口
@PostMapping("/test/form4") @ResponseBody public BookDto form4(@RequestBody BookDto bookDto) { return bookDto; }
RestTemplate 调用接口
@Test public void test15() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/form4"; BookDto body = new BookDto(1, "SpringMVC系列"); BookDto result = restTemplate.postForObject(url, body, BookDto.class); System.out.println(result); }
输出
BookDto{id=1, name='SpringMVC系列'}发送 json 格式数据:传递 java 对象,返回值为泛型
接口
@PostMapping("/test/form5") @ResponseBody public Listform5(@RequestBody List bookDtoList) { return bookDtoList; }
用 RestTemplate 调用这个接口,代码如下
@Test public void test16() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/form5"; //①:请求体,发送的时候会被转换为json格式数据 Listbody = Arrays.asList( new BookDto(1, "SpringMVC系列"), new BookDto(2, "MySQL系列")); //②:头 HttpHeaders headers = new HttpHeaders(); headers.add("header1", "v1"); headers.add("header2", "v2"); //③:请求实体 RequestEntity requestEntity = new RequestEntity(body, headers, HttpMethod.POST, URI.create(url)); //④:发送请求(请求实体,返回值需要转换的类型) ResponseEntity > responseEntity = restTemplate.exchange( requestEntity, new ParameterizedTypeReference
>() { }); //⑤:获取结果 List
result = responseEntity.getBody(); System.out.println(result); }
输出
[BookDto{id=1, name='SpringMVC系列'}, BookDto{id=2, name='MySQL系列'}]发送 json 字符串格式数据
上面 2 个 json 案例 body 都是 java 对象,RestTemplate 默认自动配上 Content-Type=application/json
但是如果 body 的值是 json 格式字符串的时候,调用的时候需要在头中明确指定 Content-Type=application/json,写法如下
@Test public void test17() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/form5"; //①:请求体为一个json格式的字符串 String body = "[{"id":1,"name":"SpringMVC系列"},{"id":2,"name":"MySQL系列"}]"; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); //③:请求实体(body,头、请求方式,uri) RequestEntity requestEntity = new RequestEntity(body, headers, HttpMethod.POST, URI.create(url)); //④:发送请求(请求实体,返回值需要转换的类型) ResponseEntity> responseEntity = restTemplate.exchange( requestEntity, new ParameterizedTypeReference
>() { }); //⑤:获取结果 List
result = responseEntity.getBody(); System.out.println(result); }
@Test public void test17() { RestTemplate restTemplate = new RestTemplate(); String url = "http://localhost:8080/chat16/test/form5"; //①:请求体为一个json格式的字符串 String body = "[{"id":1,"name":"SpringMVC系列"},{"id":2,"name":"MySQL系列"}]"; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); //③:请求实体(body,头、请求方式,uri) RequestEntity requestEntity = new RequestEntity(body, headers, HttpMethod.POST, URI.create(url)); //④:发送请求(请求实体,返回值需要转换的类型) ResponseEntity> responseEntity = restTemplate.exchange( requestEntity, new ParameterizedTypeReference
>() { }); //⑤:获取结果 List
result = responseEntity.getBody(); System.out.println(result); }
输出
[BookDto{id=1, name='SpringMVC系列'}, BookDto{id=2, name='MySQL系列'}]DELETE、PUT、OPTION 请求 DELETE 请求
public void delete(String url, Object... uriVariables); public void delete(String url, MapPUT 请求uriVariables); public void delete(URI url);
PUT 请求和 POST 请求类似,将类型改为 PUT 就可以了
OPTIONS 请求OPTIONS 请求用来探测接口支持哪些 http 方法
public Set集成 HttpClientoptionsForAllow(String url, Object... uriVariables); public Set optionsForAllow(String url, Map uriVariables); public Set optionsForAllow(URI url);
RestTemplate 内部默认用的是 jdk 自带的 HttpURLConnection 发送请求的,性能上面并不是太突出。
可以将其替换为 httpclient 或者 okhttp。
先来看下如何替换为 HttpClient。
引入 maven 配置
org.apache.httpcomponents httpclient4.5.7
创建 RestTemplate 时指定 HttpClient 配置,代码如下
public HttpClient httpClient() { HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); try { //设置信任ssl访问 SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, (arg0, arg1) -> true).build(); httpClientBuilder.setSSLContext(sslContext); HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE; SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier); Registry集成 okhttpsocketFactoryRegistry = RegistryBuilder. create() // 注册http和https请求 .register("http", PlainConnectionSocketFactory.getSocketFactory()) .register("https", sslConnectionSocketFactory).build(); //使用Httpclient连接池的方式配置(推荐),同时支持netty,okHttp以及其他http框架 PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); // 最大连接数 poolingHttpClientConnectionManager.setMaxTotal(1000); // 同路由并发数 poolingHttpClientConnectionManager.setDefaultMaxPerRoute(100); //配置连接池 httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager); // 重试次数 httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(0, true)); //设置默认请求头 List headers = new ArrayList<>(); httpClientBuilder.setDefaultHeaders(headers); return httpClientBuilder.build(); } catch (Exception e) { throw new RuntimeException(e); } } public ClientHttpRequestFactory clientHttpRequestFactory() { HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient()); // 连接超时(毫秒),这里设置10秒 clientHttpRequestFactory.setConnectTimeout(10 * 1000); // 数据读取超时时间(毫秒),这里设置60秒 clientHttpRequestFactory.setReadTimeout(60 * 1000); // 从连接池获取请求连接的超时时间(毫秒),不宜过长,必须设置,比如连接不够用时,时间过长将是灾难性的 clientHttpRequestFactory.setConnectionRequestTimeout(10 * 1000); return clientHttpRequestFactory; } public RestTemplate restTemplate(){ //创建RestTemplate的时候,指定ClientHttpRequestFactory return new RestTemplate(this.clientHttpRequestFactory()); } @Test public void test18() { RestTemplate restTemplate = this.restTemplate(); String url = "http://localhost:8080/chat16/test/get"; //getForObject方法,获取响应体,将其转换为第二个参数指定的类型 BookDto bookDto = restTemplate.getForObject(url, BookDto.class); System.out.println(bookDto); }
引入 maven 配置
com.squareup.okhttp3 okhttp4.3.1
创建 RestTemplate
new RestTemplate(new OkHttp3ClientHttpRequestFactory());
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)