访问外部接口的几种方式
访问外部接口指的是:使用代码发出Http请求,访问外部资源。
- 使用SpringBoot的RestTemplate
- 使用SpringCloud的Feign
- 使用Spring WebFlux的WebClient
由于本人菜鸟一个,这三种方式的联系与差别并未深究,只知道RestTemplate是同步阻塞式模型
WebClient是异步回调式模型,同步阻塞意味着程序会一直等待请求响应,可能会对运行速度有影响。
本篇博客只介绍RestTemplate,其他两个方式的学习留作以后摸索。
RestTemplate的使用
RestTemplate依赖在Web场景启动器中,所以无需导入maven依赖
RestTemplate的构建方式
@Configuration public class RestTemplateConfig { /* 使用RestTemplateBuilder构建RestTemplate,可以定制化一些内容,拦截器,消息转换器等 .setConnectTimeout(),指的是http建立连接阶段,即允许的最大握手时间 .setReadTimeout(),指的是设置读超时时间 .rootUri(),指的是如果发送的请求不是以http开头,则请求地址会自动带上rootUri根路径 最后build()返回RestTemplate,记得好像是使用了建造者模式。 */ @Bean public RestTemplate getRestTemplate(RestTemplateBuilder builder){ RestTemplate restTemplate = builder.setConnectTimeout(Duration.ofMillis(3000)) .setReadTimeout(Duration.ofMillis(5000)) .rootUri("http://api.example.com/") .build(); return restTemplate; } /* 第二种方式是直接使用RestTemplate对象,使用默认设置。 两种方式任选一种。 */ @Bean public RestTemplate getRestTemplate(){ return new RestTemplate(); } }
RestTemplate中的方法使用
因为RestTemplate类是为调用REST服务而设计的,所以它的主要方法与REST的请求方式紧密相连
下图是rest请求方式对应的RestTemplate中方法这次主要讲Get,Post请求方式对应的RestTemplate中的方法,再加上exchange方法 Get请求 1)getForEntity方法,三个重载方法,之后的每个方法都有三个重载方法 /* 参数1:String类型或URI类型的请求地址 参数2:指定返回的实体类型,class对象 参数3:uri参数,可以是变长数组或map 返回值:ResponseEntity<T>是Spring对HTTP响应的封装,包括了几个重要的元素, 如响应码、contentType、contentLength、response header信息,response body信息等 */ public <T> ResponseEntity<T> getForEntity (String url, Class<T> responseType, Object... uriVariables) public <T> ResponseEntity<T> getForEntity (String url, Class<T> responseType, Map<String, ?> uriVariables) public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) 案例: ResponseEntity<Book> responseEntity = restTemplate.getForEntity ("http://127.0.0.1:8080/getbook?bookname={1}", Book.class, "java"); //响应体转换为Book类型 Book book = responseEntity.getBody(); //获取响应状态码 int statusCodeValue = responseEntity.getStatusCodeValue(); //获取响应头信息 HttpHeaders headers = responseEntity.getHeaders(); 2)getForObject方法 /* 参数1:String类型或URI类型的请求地址 参数2:指定返回的实体类型,class对象 参数3:uri参数,可以是变长数组或map 返回值:responseType指定的Object类型 */ public <T> T getForObject (String url, Class<T> responseType, Object... uriVariables) public <T> T getForObject (String url, Class<T> responseType, Map<String, ?> uriVariables) public <T> T getForObject(URI url, Class<T> responseType) 案例: Map<String, String> map = new HashMap<>(); map.put("bookname", "大主宰"); Book book = restTemplate.getForObject("http://127.0.0.1:8080/getbook?bookname={bookname}", Book.class, map); Post请求 1)postForEntity方法 /* 参数1:同getForEntity方法 参数2:请求body,可以是HttpEntity类型(可设置request header),或其它Object类型 参数3:同getForEntity方法 参数4:同getForEntity方法 返回值:同getForEntity方法 */ public <T> ResponseEntity<T> postForEntity (String url, Object request, Class<T> responseType, Object... uriVariables) public <T> ResponseEntity<T> postForEntity (String url, Object request, Class<T> responseType, Map<String, ?> uriVariables) public <T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType) 案例: //参数是Book类型,返回值是ResponseEntity<Book>类型 ResponseEntity<Book> responseEntity = restTemplate.postForEntity ("http://127.0.0.1:8080/updateBook", book, Book.class); Book book = responseEntity.getBody(); int statusCodeValue = responseEntity.getStatusCodeValue(); HttpHeaders headers = responseEntity.getHeaders(); 案例2: //post方法获取List数据 List<ProductTypeVO> voList = Lists.newArrayList(); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON_UTF8); headers.add("key1","value1"); headers.add("key2","value2"); HttpEntity<String> entity = new HttpEntity<>("", headers); ProductTypeVO[] responseEntity = restTemplate.postForObject (url,entity,ProductTypeVO[].class); if (null != responseEntity) { voList = Arrays.asList(responseEntity); } 2)postForObject方法 /* 参数1:同getForObject方法 参数2:请求body,可以是HttpEntity类型(可设置request header),或其它Object类型 参数3:同getForObject方法 参数4:同getForObject方法 返回值:同getForObject方法 */ public <T> T postForObject (String url, Object request, Class<T> responseType, Object... uriVariables) public <T> T postForObject (String url, Object request, Class<T> responseType, Map<String, ?> uriVariables) public <T> T postForObject(URI url, Object request, Class<T> responseType) 案例: //参数是Book类型,返回值也是Book类型 Book book = restTemplate.postForObject("http://127.0.0.1:8080/updatebook", book, Book.class); Exchange方法 public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType, Object... uriVariables) public <T> ResponseEntity<T> exchange(String url, HttpMethod method, @Nullable HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType, Map<String, ?> uriVariables) 1)可以支持多种Http方法,只需要在参数中指定即可 2)可以在请求中增加header和body信息,返回类型是ResponseEntity, 可以从中获取响应的状态码,header和body等信息 案例: HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.set("MyRequestHeader", "MyValue"); //处理可以设置请求头,还可以设置请求体body,HttpEntity中类型为MultiValueMap<String, String> HttpEntity requestEntity = new HttpEntity(requestHeaders); HttpEntity<String> response = restTemplate.exchange( "http://example.com/hotels/{hotel}", HttpMethod.GET, requestEntity, String.class, "42"); //响应头信息 String responseHeader = response.getHeaders().getFirst("MyResponseHeader"); //获取响应体数据 String body = response.getBody();
Execute方法说明
这个方法要单独拿出来讲,我们可以看restTemplate类发现,get,post,exchange等方法内部都会调用
execute方法,而在该方法内部仅仅将String类型的URI转为java.net.URI类型后就调用了doExecute方法
所以doExecute方法才是关键所在,下图是发出http请求的大概过程。然后在该方法中需要关注RequestCallback 和 ResponseExtractor两个类,这两个类作用有点复杂,
我也不太懂就不瞎写了,以后有需要再去了解。ClientHttpRequestFactory接口
它是个函数式接口,用于根据URI
和HttpMethod
创建出一个ClientHttpRequest
来发送请求ClientHttpRequest它代表请求的客户端,该接口继承自HttpRequest、HttpOutputMessage,只有一个public ClientHttpResponse execute() throws IOException
方法。
其中Netty、HttpComponents、OkHttp3,HttpUrlConnection对它都有实现
只要调用该接口实现类的execute()就能发送rest请求了。
默认情况下,我们使用的是SimpleClientHttpRequestFactory,使用的是JDK内置的java.net.URLConnection
作为client客户端。
这部分对我来说还是太晦涩,暂时先不看,知道有这个接口就行,如果想指定某个该接口的实现类
可以使用相应的构造函数创建RestTemplate,或者通过RestTemplateBuilder设置指定的工厂。配置RestTemplate的自定义拦截器
注册拦截器
@Bean public RestTemplate restTemplate(RestTemplateBuilder builder) { return builder .additionalInterceptors(new RequestResponseLoggingInterceptor()) .build(); }
创建自定义拦截器,需要实现ClientHttpRequestInterceptor接口
public class RequestResponseLoggingInterceptor implements ClientHttpRequestInterceptor { private final Logger log = LoggerFactory.getLogger(this.getClass()); @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException{ logRequest(request, body); ClientHttpResponse response = execution.execute(request, body); logResponse(response); //Add optional additional headers response.getHeaders().add("headerName", "VALUE"); return response; } private void logRequest(HttpRequest request, byte[] body) throws IOException{ if (log.isDebugEnabled()) { log.debug("=========request begin========="); log.debug("URI : {}", request.getURI()); log.debug("Method : {}", request.getMethod()); log.debug("Headers : {}", request.getHeaders()); log.debug("Request body: {}", new String(body, "UTF-8")); log.debug("=========request end========="); } } private void logResponse(ClientHttpResponse response) throws IOException{ if (log.isDebugEnabled()) { log.debug("=========response begin========="); log.debug("Status code : {}", response.getStatusCode()); log.debug("Status text : {}", response.getStatusText()); log.debug("Headers : {}", response.getHeaders()); log.debug("Response body: {}", StreamUtils.copyToString (response.getBody(), Charset.defaultCharset())); log.debug("=========response end========="); } } }
参考链接
2023-3-20更新
一个使用案例
@RequestMapping("/testUrl")
public Result<Object> testUrl(@RequestParam("id") String id){
Result<Object> res;
EamsBusinessDataInterface dataInterface = eamsBusinessDataInterfaceService.getById(id);
if(ObjectUtil.isNotEmpty(dataInterface)){
HttpMethod method = HttpMethod.resolve(dataInterface.getConnectionType());
if (ObjectUtil.isNotEmpty(method)){
HttpHeaders headers = new HttpHeaders();
ResponseEntity<Result> responseEntity = null;
String param = dataInterface.getParams().replaceAll("\n", "");
//服务端响应的内容格式
headers.setContentType(MediaType.APPLICATION_JSON);
ArrayList<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.APPLICATION_JSON);
headers.setAccept(mediaTypes);
if(method.matches("POST")){
HttpEntity entity = new HttpEntity<>(param, headers);
responseEntity = eamsRestTemplate.exchange(dataInterface.getTestUrl(), method, entity, Result.class);
}else {
HttpEntity entity = new HttpEntity<>(headers);
//拼接get参数
HashMap<String,String> paramMap = JSONUtil.toBean(param, HashMap.class);
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
paramMap.entrySet().forEach(i->{
params.add(i.getKey(),i.getValue());
});
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(dataInterface.getTestUrl());
URI uri = builder.queryParams(params).build().encode().toUri();
responseEntity = eamsRestTemplate.exchange(uri, method, entity, Result.class);
}
if (ObjectUtil.isNotEmpty(responseEntity)&&ObjectUtil.equals(responseEntity.getStatusCode().value(), HttpStatus.HTTP_OK)){
//设置调用结果的返回值和状态
Result result = responseEntity.getBody();
if (ObjectUtil.isNotEmpty(result)){
if (!result.isSuccess()){
dataInterface.setReturnResult(result.getMessage());
}else {
dataInterface.setReturnResult(JSON.toJSONString(result.getResult()));
}
}else{
dataInterface.setReturnResult("无数据返回");
}
if (ObjectUtil.isNotEmpty(result)&&ObjectUtil.equals(result.getCode(),HttpStatus.HTTP_OK)){
dataInterface.setStatus(String.valueOf(HttpStatus.HTTP_OK));
eamsBusinessDataInterfaceService.updateById(dataInterface);
return Result.OK("测试成功");
}
}
}
}
dataInterface.setStatus(String.valueOf(HttpStatus.HTTP_INTERNAL_ERROR));
res = Result.error("测试失败");
eamsBusinessDataInterfaceService.updateById(dataInterface);
return res;
}
注意点:
1.如果是post请求,那么请求参数可以放在header中,如果是get请求,那么请求参数需要拼接在url中
2.restTemplate请求在请求https时,一般需要服务器方的证书才能发起请求,
此时要么配置服务端的证书,要么配置信任所有https请求
3.有时候restTemplate请求接口返回的数据乱码,则需要配置restTemplate中的媒体类型的编码
4.如果报错no suitable HttpMessageConverter found for response type
是因为服务端返回的数据格式你客户端消息转换器处理不了
例如服务端设置response.setContentType("text/html"),表示服务端返回一个html文件
但是客户端的消息转换器httpMessageConverter的媒体类型处理不了这个媒体类型数据,所以报错
5.request的contentType和response的contentType,这个值是用来描述你传给对方的数据类型
而不是期待对方传递的数据类型,你携带json参数或者返回json数据,就写“application/json”
文章浏览
- spring RestTemplate封装,发送http、https以及https携带证书请求
- 处理restTemplate的messageConverters设置StringHttpMessageConverter
- no suitable HttpMessageConverter found for response type
- 被坑过后才知道学习HttpMessageConverter有多重要
- RestTemplate优雅的发送Get请求
- spring boot 使用RestTemplate信任所有https请求
- Https请求报错:unable to find valid certification path to requested target
- spring boot 使用RestTemplate通过证书认证访问https实现SSL请求
- RestTemplate 微信接口 text/plain HttpMessageConverter