简介
微服务中的系统都在独立的运行中,通过各个服务系统之间的写协作完成业务功能。服务系统间使用多种跨进程的方式通信协作,而Restful风格的网络请求是最常见的。
Spring Cloud提供的方式:
- RestTemplate
- 方便调用别的第三方的http服务,适用于异构环境
- Feign
- 一种负载均衡的HTTP客户端, 使用Feign调用API就像调用本地方法一样,从避免了调用目标微服务时。
RestTemplate
@Bean
// 开启负载均衡
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
使用资源地址调用服务
String url ="http://provider/getHi";
String respStr = restTemplate.getForObject(url, String.class);
get 请求处理
getForEntity
ResponseEntity是Spring对HTTP请求响应的封装,包括了几个重要的元素,如响应码、contentType、contentLength、响应消息体等。
ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://provider/getHi", String.class);
responseEntity.getBody();
传参调用
- 使用占位符
String url ="http://provider/getObjParam?name={1}"; ResponseEntity<Person> entity = restTemplate.getForEntity(url, Person.class,"hehehe...");
- 使用map
String url ="http://provider/getObjParam?name={name}";
Map<String, String> map = Collections.singletonMap("name", " memeda");
ResponseEntity<Person> entity = restTemplate.getForEntity(url, Person.class,map);
post请求处理
String url ="http://provider/postParam";
Map<String, String> map = Collections.singletonMap("name", " memeda");
ResponseEntity<Person> entity = restTemplate.postForEntity(url, map, Person.class);
postForLocation
返回值为URI对象,因为Post请求最常用来添加数据,如果需要将刚刚添加成功的数据的URL返回来,此时就可以使用这个方法
String url ="http://provider/postParam";
Map<String, String> map = Collections.singletonMap("name", " memeda");
URI location = restTemplate.postForLocation(url, map, Person.class);
System.out.println(location);
exchange
可以自定义http请求的头信息,同时保护get和post方法
拦截器
需要实现ClientHttpRequestInterceptor
接口
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;
}
添加到resttemplate中
@Bean
@LoadBalanced
RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(new LoggingClientHttpRequestInterceptor());
return restTemplate;
}
feign
这样已经加入spring容器中,使用自动装配就可以使用了。
接口加注解,方法加注解
@FeignClient(name = "service-sms",
fallback = 降级的类.class,
fallbackFactory = FileServiceFallbackFactory.class,
configuration = OAuth2FeignConfig.class
)
public interface SmsClient {
/**
* 按照短信模板发送验证码
* @param smsSendRequest
* @return
*/
@RequestMapping(value="/send/alisms-template", method = RequestMethod.POST)
public ResponseResult sendSms(@RequestBody SmsSendRequest smsSendRequest);
}
开启feign的功能
@SpringBootApplication
@EnableFeignInterceptor
@EnableFeignClients
public class UserConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(UserConsumerApplication.class, args);
}
}
fallbackFactory就和标准的熔断降级差不多,可以把异常信息全部带过来,但是需要我们对异常做转换,常规的exception还是无法获取body部分的。
@Component
public class FileServiceFallbackFactory implements FallbackFactory<SmsClient> {
@Override
public SmsClient create(final Throwable throwable) {
FeignException ex = (FeignException) throwable;
JSONObject jsonObject = JSONObject.parseObject(ex.contentUTF8());
return new TestApi() {
@Override
public String sendSms(SmsSendRequest smsSendRequest) {
return jsonObject.getString("message");
}
};
}
}
上下文中获取token,将token放在请求的header头部中,因为调用远程服务的api的时候,也要经过common模块的资源服务器的token拦截校验
@Configuration
public class OAuth2FeignConfig implements RequestInterceptor {
private static final String client_credentials = "client_credentials";
@Override
public void apply(RequestTemplate requestTemplate) {
//从上线文中获取token
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String header = requestAttributes.getRequest().getHeader(HttpHeaders.AUTHORIZATION);
requestTemplate.header(HttpHeaders.AUTHORIZATION, header);
}
}
客户端要熔断降级必须在yaml文件里进行配置,否则无法进入fallbackFactory
feign:
hystrix:
enabled: true
全局配置异常
@Configuration
public class FeignErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(final String methodKey, final Response response) {
try {
String message = Util.toString(response.body().asReader());
try {
JSONObject jsonObject = JSONObject.parseObject(message);
// 包装成自己自定义的异常,这里建议根据自己的代码改
return new MyException(jsonObject.getString("message"), jsonObject.getInteger("code"));
} catch (JSONException e) {
e.printStackTrace();
}
} catch (IOException ignored) {
}
return decode(methodKey, response);
}
}
区别
请求方式不一样
-
RestTemplate需要每个请求都拼接url+参数+类文件,灵活性高但是消息封装臃肿。
-
feign可以伪装成类似SpringMVC的controller一样,将rest的请求进行隐藏,不用再自己拼接url和参数,可以便捷优雅地调用HTTP API。
底层实现方式不一样
-
RestTemplate在拼接url的时候,可以直接指定ip地址+端口号,不需要经过服务注册中心就可以直接请求接口;也可以指定服务名,请求先到服务注册中心(如nacos)获取对应服务的ip地址+端口号,然后经过HTTP转发请求到对应的服务接口(注意:这时候的restTemplate需要添加@LoadBalanced注解,进行负载均衡)。
-
Feign的底层实现是动态代理,如果对某个接口进行了@FeignClient注解的声明,Feign就会针对这个接口创建一个动态代理的对象,在调用这个接口的时候,其实就是调用这个接口的代理对象,代理对象根据@FeignClient注解中name的值在服务注册中心找到对应的服务,然后再根据@RequestMapping等其他注解的映射路径构造出请求的地址,针对这个地址,再从本地实现HTTP的远程调用。