RestTemplate
服务之间是通过http协议调用,虽然效率较低但是具有良好的可扩展性和可维护性。可以通过httpclient,okhttp等调用但是有重复代码,较难维护,使用不便。现在流行的方式有RestTemplate和Feign两种方式。
RestTemplate是同步客户端执行HTTP请求,暴露出一个简单的、模板等方法在基本HTTP客户机API库JDK HttpURLConnection,Apache HttpComponents等等。
RestTemplate常用的方法:
get 请求处理
getForEntity
getForEntity方法的返回值是一个ResponseEntity,ResponseEntity是Spring对HTTP请求响应的封装,包括了几个重要的元素,如响应码、contentType、contentLength、响应消息体等。
调用方:
@RequestMapping("/getEntity")
public ResponseEntity<String> getEntity() {
List<InstanceInfo> list = eurekaClient.getInstancesByVipAddress("producer", false);
if (list != null && !list.isEmpty()) {
InstanceInfo instanceInfo = list.get(0);
String url = "http://"+instanceInfo.getIPAddr()+":"+ instanceInfo.getPort();
ResponseEntity<String> forEntity = restTemplate.getForEntity(url + "/hello", String.class);
System.out.println(forEntity);
return forEntity;
}
return null;
}
forEntity为:
<200,hello,8000,[Content-Type:"text/plain;charset=UTF-8", Content-Length:"10", Date:"Sat, 25 Mar 2023 11:57:24 GMT", Keep-Alive:"timeout=60", Connection:"keep-alive"]>
传参调用
以下例子都是调用方。
使用占位符:
@RequestMapping("/getEntityForParam")
public ResponseEntity<String> getEntityForParam() {
List<InstanceInfo> list = eurekaClient.getInstancesByVipAddress("producer", false);
if (list != null && !list.isEmpty()) {
InstanceInfo instanceInfo = list.get(0);
String url = "http://"+instanceInfo.getIPAddr()+":"+ instanceInfo.getPort();
ResponseEntity<String> forEntity = restTemplate.getForEntity(url + "/hello?name={1}", String.class,"张三");
System.out.println(forEntity);
return forEntity;
}
return null;
}
使用map:
@RequestMapping("/getEntityForMap")
public ResponseEntity<String> getEntityForMap() {
List<InstanceInfo> list = eurekaClient.getInstancesByVipAddress("producer", false);
if (list != null && !list.isEmpty()) {
InstanceInfo instanceInfo = list.get(0);
String url = "http://"+instanceInfo.getIPAddr()+":"+ instanceInfo.getPort();
Map<String,Object> map = Collections.singletonMap("name","李四");
ResponseEntity<String> forEntity = restTemplate.getForEntity(url + "/hello?name={name}", String.class,map);
System.out.println(forEntity);
return forEntity;
}
return null;
}
/hello?name={name}里面的name要和Map里面的key相同.
getForObject
getForObject和getForEntity使用方式和传参方式都是相同的,只是返回值有区别。
post 请求处理
使用postForObject和postForEntity发送post请求,使用方式和传参和使用get请求相同。
postForLocation
postForLocation的返回值是URI。
调用方:
@RequestMapping("/postForLocation")
public URI postForLocation() {
List<InstanceInfo> list = eurekaClient.getInstancesByVipAddress("producer", false);
if (list != null && !list.isEmpty()) {
InstanceInfo instanceInfo = list.get(0);
String url = "http://"+instanceInfo.getIPAddr()+":"+ instanceInfo.getPort();
URI forEntity = restTemplate.postForLocation(url + "/hello?name={1}",null, String.class,"张三");
System.out.println(forEntity);
return forEntity;
}
return null;
}
服务提供方:
@RequestMapping("/hello")
public String hello(String name, HttpServletResponse response) {
response.addHeader("Location", "/hello?123");
return "hello," + name + "," + port;
}
可以得到postForLocation的结果是response设置的Location头,结果如下:
如果服务提供方不设置头信息则postForLocation返回值为null。
exchange
exchange可以自定义http请求的头信息,同时保护get和post方法.
调用方:
@RequestMapping("/exchange")
public String exchange() {
List<InstanceInfo> list = eurekaClient.getInstancesByVipAddress("producer", false);
if (list != null && !list.isEmpty()) {
InstanceInfo instanceInfo = list.get(0);
String url = "http://"+instanceInfo.getIPAddr()+":"+ instanceInfo.getPort();
ResponseEntity<String> responseEntity = restTemplate.exchange(url + "/hello?name={1}", HttpMethod.POST,null , String.class, "张三");
System.out.println(responseEntity);
return responseEntity.getBody();
}
return null;
}
参数传递和postForObject,postForEntity相同,可以指定http方法,比如用GET方式调用。第三个参数是请求头,这里为null。
拦截RestTemplate请求
如果需要拦截RestTemplate调用的请求,实现ClientHttpRequestInterceptor这个接口后将实现类注册到RestTemplate中。
例子:
public class LoggingClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
System.out.println("拦截啦!!!");
System.out.println(request.getURI());
ClientHttpResponse response = execution.execute(request, body);
System.out.println(response.getHeaders());
return response;
}
}
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(new LoggingClientHttpRequestInterceptor());
return restTemplate;
}
访问http://localhost:8001/exchange,看到:
这里的拦截范围是从restTemplate调用开始到返回为止。上面用restTemplate调用必须写清服务的IP地址和端口,这在单个服务时还行,但是对于微服务来说就不够了,可能会增加服务和删除服务,服务地址会动态变化,而且对于负载均衡来说也不好,要直接使用restTemplate基本是和ribbon结合起来使用的。下次再看ribbon是怎么使用的。