这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战
背景
最近在微服务的调用上涉及到一个场景,就是在多个微服务中,当某种业务条件满足的前提下,会触发当前服务去拉取多个微服务中的同一个接口去获取信息,平常我们使用的服务间调用都是通过feign
的方式进行服务间http
请求调用,但是在这种场景下,由于多个服务地址,不知道多到什么程度,是随着业务系统的业务逻辑来决定的,这时就得使用上RestTemplate
在SpringBoot
中的相应功能。
引入
RestTemplate
是Spring
提供的用来进行http
请求的客户端,它直接提供了请求的api
接口,能够有效的提高客户端的编写效率,再也不用去写httpClient
的工具类了。在Springboot
中使用RestTemplate
和在普通服务中使用RestTemplate
有所不同。
如果只是想让RestTemplate
拥有http
请求的能力,直接创建一个RestTemplate
,然后直接调用现有api
即可。
public static void main(String[] args) {
RestTemplate restTemplate = new RestTemplate();
// get请求
restTemplate.getForObject("对应请求的url地址", String.class);
}
复制代码
在springboot
中,RestTemplate
还可以具有获取注册中心对应服务地址的功能,使用上也非常简单。只需要在注入到Spring
容器中的restTemplate
上添加@LoadBalanced
注解后,在使用时就可以使用服务名进行http
请求的调用。
@Bean("loadBalanceRestTemplate")
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
复制代码
具体调用:
/**
* post请求方法 通过restTemplate做负载均衡,请求地址以http://服务名/..接口地址
*
* @param object 请求实体
* @param url 请求地址
* @return java.lang.String
* @author xiaocainiaoya
* @date 2021/9/23 11:19 上午
*/
public String loadBalancePostRequest(Object object, String url) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
String jsonStr = JSON.toJSONString(object);
log.info("==================开始请求远程接口==================");
log.info("请求地址为:{}, 请求参数为:{}", url, jsonStr);
HttpEntity<String> request = new HttpEntity<>(jsonStr, headers);
String result = loadBalanceRestTemplate.postForObject(url, request, String.class);
log.info("返回结果:{}", result);
return result;
}
复制代码
那么,问题来了,为什么添加了@LoadBalance
之后,TestTemplate
就具备了通过服务名进行请求的能力。
分析
首先根据springboot
中的设计结构,基本上组件都是通过auto
的方式添加的,@LoadBalance
也不例外。
LoadBalancerAutoConfiguration
是@LoadBalance
生效的关键配置类,它将所有标记的对象都收集到了这里。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
// 这里是一个全局的收集动作,会将所有的容器中的`@LoadBalanded`标记的容器中对象都收集到这里。
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
}
复制代码
在实现上实际上是通过发起请求之前经过一系列的拦截器为这里收集到的restTemplate
进行对应的请求地址装换。这是一个扩展点,用户也可以自己编写对应的拦截器,然后添加到需要的restTempalte
中,比如需要一些日志拦截、转码、请求加密、响应解密等场景。
这里产生作用的拦截器是LoadBalancerInterceptor
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer,
LoadBalancerRequestFactory requestFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
// for backwards compatibility
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
// 获取对应的服务名
String serviceName = originalUri.getHost();
Assert.state(serviceName != null,"Request URI does not contain a valid hostname: " + originalUri);
// 拆解出服务名,使用loadBalanceClient进行具体的处理
return this.loadBalancer.execute(serviceName,this.requestFactory.createRequest(request, body, execution));
}
}
复制代码
具体的处理是在子类RibbonLoadBalancerClient
中,这里实际上还涉及到负载均衡的一些策略等,然后获取到对应的服务实例,然后去发起对应的请求。
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
throws IOException {
// 根据服务名获取对应的负载均衡器,这与策略相关
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// 根据策略获取到对应的服务实例
Server server = getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonServer ribbonServer = new RibbonServer(serviceId, server,
isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
return execute(serviceId, ribbonServer, request);
}
复制代码
总结
RestTemplate
打入容器时加上LoadBalance
注解后,由于启动SpingBoot
启动时触发auto
自动装配机制,加载了对应的配置类,使得将有注解标记的RestTemplate
对象都进行了收集,并对这些对象都添加了对应的拦截器,该拦截器的实现上进行了服务名到http
直接地址的转换。