乐趣区

关于vue.js:如何在RestTemplate-结合-Ribbon-使用

一、应用 RestTemplate 与整合 Ribbon
Spring 提供了一种简略便捷的模板类来进行 API 的调用,那就是 RestTemplate。

  1. 应用 RestTemplate
    首先咱们看看 GET 申请的应用形式:在 fsh-house 服务的 HouseController 中减少两个接口,一个通过 @RequestParam 来传递参数,返回一个对象信息; 另一个通过 @PathVarable 来传递参数,返回一个字符串。尽量通过两个接口组装不同的模式,具体如上面代码所示。

@GetMapping(“/data”)public HouseInfo getData(@RequestParam(“name”) String name) {

return new HouseInfo(1L, "上海","虹口","东体小区");

}
@GetMapping(“/data/{name}”)
public String getData2(@PathVariable( “name”) String name) {

return name;

}
在 fsh-substitution 服务中用 RestTemplate 来调用咱们刚刚定义的两个接口,具体如上面代码所示。

@GetMapping(“/data”)
public HouseInfo getData(@RequestParam(“name”) String name) {

return restTemplate.getForobject(");

}

@GetMapping(“/data/{name}”)
public String getData2(@PathVariable(“name”) String name) {
return restTemplate.getForobject(

  "{name}",String.class,name);

}
获取数据后果通过 RestTemplate 的 getForObject 办法 (具体如上面代码所示) 来实现,此办法有三个重载的实现:

url:申请的 API 地址,有两种形式,其中一种是字符串,另一种是 URL 模式。
responseType:返回值的类型。
uriVariables : PathVariable 参数,有两种形式,其中一种是可变参数,另一种是 Map 模式。
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) ;
除了 getForObject,咱们还能够应用 getForEntity 来获取数据,代码如上面代码所示。

@GetMapping(” /data”)public HouseInfo getData(@RequestParam(“name”) String name) {

ResponseEntity<HouseInfo> responseEntity = restTemplate.getForEntity("http:/ /localhost: 8081 /house/ data?name="+name, HouseInfo.class) ;
if (responseEntity.getStatusCodeValue() == 200) {return responseEntity.getBody();
}
return nu1l ;

}
getForEntity 中能够获取返回的状态码、申请头等信息,通过 getBody 获取响应的内容。其余的和 getForObject 一样,也是有 3 个重载的实现。

接下来看看怎么应用 POST 形式调用接口。在 HouseController 中减少一个 save 办法用来接管 HouseInfo 数据,如上面代码所示。

@PostMapping(“/save”)public Long addData(@RequestBody HouseInfo houseInfo) {

System.out.println(houseInfo. getName());
return 1001L;

}
接着写调用代码,用 postForObject 来调用,如上面代码所示。

@GetMapping(“/save”)public Long add(){

HouseInfo houseInfo = new HouseInfo();
houseInfo.setCity("上海");
houseInfo.setRegion("虹口");
houseInfo.setName("XXX");
Long id = restTemplate.postFor0bject("http: //1ocalhost:8081/ house/save",houseInfo,Long.class);
return id;

}
postForObject 同样有 3 个重载的实现。除了 postForObject 还能够应用 postForEntity 办法,用法都一样,如上面代码所示。

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);
除了 get 和 post 对应的办法之外,RestTemplate 还提供了 put、delete 等操作方法,还有一个比拟实用的就是 exchange 办法。exchange 能够执行 get、post、put、delete 这 4 种申请形式。

  1. 整合 Ribbon
    在 Spring Cloud 我的项目中集成 Ribbon 只须要在 pom.xml 中退出上面的依赖即可,其实也能够不必配置,因为 Eureka 中曾经援用了 Ribbon,如上面代码所示。

<dependency>

<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>

</dependency>
这个配置咱们加在 fangjia-fsh-substitution-service 中。

二、RestTemplate 负载平衡示例
对之前的代码进行一些小革新,输入一些内容,证实咱们集成的 Ribbon 是无效的。

革新 hello 接口,在接口中输入以后服务的端口,用来辨别调用的服务,如上面代码所示。

@RestController@RequestMapping(“/house”)public class HouseController {

@Value("${server .port}")
private String serverPort; 

@GetMapping("/hel1o")
public String hel1o(){return "Hello"+serverPort;}

}
上述代码别离以 8081 和 8083 端口启动两个服务,前面要用到。

接着革新 callHello 接口的代码,将调用后果输入到控制台,如上面代码所示。

@RestController@RequestMapping(“/substitution”)public class Substitut ionController{

@Autowired
private RestTemplate restTemplate;
@GetMapping ("/cal1Hel1o")
public String cal1Hello(){
    String result = restTemplate. getFor0bject(");
    System.out.print1n("调用后果:" + result);
    return result ;
}

}
测试步骤如下:

重启服务。
拜访 接口。
查看控制台输入,此时就晓得负载有没有起作用了。

三、@LoadBalanced 注解原理
置信大家肯定有一个疑难:为什么在 RestTemplate 上加了一个 @LoadBalanced 之后,RestTemplate 就可能跟 Eureka 联合了,能够应用服务名称去调用接口,还能够负载平衡?

这功绩应归于 Spring Cloud 给咱们做了大量的底层工作,因为它将这些都封装好了,咱们用起来才会那么简略。框架就是为了简化代码,提高效率而产生的。

次要的逻辑就是给 RestTemplate 减少拦截器,在申请之前对申请的地址进行替换,或者依据具体的负载策略抉择服务地址,而后再去调用,这就是 @LoadBalanced 的原理。

上面咱们来实现一个简略的拦截器,看看在调用接口之前会不会进入这个拦截器。咱们不做任何操作,就输入一句话,证实能进来就行了。如上面代码所示。

public class MyLoadBalancerInterceptor implements ClientHttpRequestInterceptor{

@Override
public ClientHttpResponse intercept (final HttpRequest request ,
    final byte[] body, final ClientHttpRequestExecution
        execution) throws IOException{final URI originalUri = request.getURI(); 
    String serviceName = originalUri.getHost();
    System.out.println("进入自定义的申请拦截器中"+serviceName);
    return execution.execute(request, body);
}

}
拦截器好了之后,咱们再定义一个注解,复制 @LoadBalanced 的代码,改个名称就能够了,如上面代码所示。

@Target({ElementType.FIELD,ElementType.PARAMETER,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface MyLoadBalanced {
}
而后定义一个配置类,来给 RestTemplate 注人拦截器,如上面代码所示。

@Configurationpublic class MyLoadBalancerAutoConfiguration{

@MyLoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();

@Bean
public MyLoadBalancerInterceptor myLoadBalancerInterceptor() {return new MyLoadBalancerInterceptor();
}

@Bean
public SmartInitializingSingleton myLoadBalancedRestTemplateInitializer() {return new SmartInitializingSingleton() {
        @Override
        public void afterSingletonsInstantiated() {
            for (RestTemplate restTemplate :
                MyLoadBalancerAutoConfiguration.this.restTemplates){
                List<ClientHttpRequestInterceptor> list = new
                    ArrayList<>(restTemplate.getInterceptors());
                list.add(myLoadBalancerInterceptor());
                restTemplate.setInterceptors(list);
            }
        }
    };
}

}
保护一个 @MyLoadBalanced 的 RestTemplate 列表,在 SmartlnitializingSingleton 中对 RestTemplate 进行拦截器设置。

而后革新咱们之前的 RestTemplate 配置,将 @LoadBalanced 改成咱们自定义的 @MyLoadBalanced,如上面代码所示。

@Bean//@LoadBalanced@MyLoadBalancedpublic RestTemplate getRestTemplate(){

return new RestTemplate() ;

}
重启服务,拜访 就能够看到控制台的输入了,这证实在接口调用的时候会进人该拦截器,输入如下:

通过这个小案例咱们就可能分明地晓得 @LoadBalanced 的工作原理。接下来咱们来看看源码中是怎么的一个逻辑。

首先看配置类,如何为 RestTemplate 设置拦截器,代码在 spring- cloud commonsjar 中的 orgspringframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration 类外面通过查看 LoadBalancerAutoConfiguration 的页游 + 源码,能够看到这里也是保护了一个 @LoadBalanced 的 RestTemplate 列表,如上面代码所示。

@LoadBalanced@Autowired(required = false)private List<RestTemplate> restTemplates = Collections.emptyList();@Beanpublic SmartInitializingSingleton loadBalancedRestTemplateInitializer(

    final List<RestTemplateCustomizer> customizers) {return new SmartInitializingSingleton() {
    @Override
    public void afterSingletonsInstantiated() {
        for(RestTemplate restTemplate :
            LoadBalancerAutoConfiguration.this.restTemplates) {for (RestTemplateCustomizer customizer:customizers) {customizer.customize(restTemplate);
            
            }
        }
    }
};

}
上面看看拦截器的配置。能够晓得,拦截器用的是 LoadBalancerInterceptor,RestTemplate Customizer 用来增加拦截器,如上面代码所示。

@Configuration @conditionalOnMissingClass(“org.springframework.retry.support.RetryTemplate”)static class LoadBalancerInterceptorConfig {

@Bean
public LoadBalancerInterceptor ribbonInterceptor (
        LoadBalancerClient loadBalancerClient , 
        LoadBalancerRequestFactory requestFactory) 
    return new LoadBalancer Interceptor(loadBalancerClient,requestFactory);
}

@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {return new RestTemplateCustomizer() {
        @Override
        public void customize(RestTemplate restTemplate) {
            List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
            list.add(loadBalancer Interceptor);
            restTemplate. setInterceptors(list);
        }
    };
}

}
拦截器的代码在 org.www.sangpi.comspringframework.cloud.client.loadbalancerLoadBalancerInterceptor 中,如上面代码所示。

public class LoadBalancerInterceptor imp1ements

    ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer ,
        LoadBalancerRequestFactory requestFactory){
    this. loadBalancer = loadBalancer;
    this. requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {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 != nu11, "Request URI does not contain a valid hostname:" + originalUri) ; 
    return this.loadBalancer.execute (serviceName ,
        requestFactory . createRequest(request, body, execution));
}

}
次要的逻辑在 intercept 中,执行交给了 LoadBalancerClient 来解决,通过 LoadBalancer RequestFactory 来构建一个 LoadBalancerRequest 对象,如上面代码所示。

public LoadBalancerRequest<ClientHttpResponse> createRequest(final

    HttpRequest request, final byte[] body,
        final ClientHttpRequestExecution execution) {return new LoadBalancerRequest<ClientHttpResponse>() {
    @Override
    public ClientHttpResponse apply(final ServiceInstance instance)
            throws Exception {
        HttpRequest serviceRequest = new 
            ServiceRequestwrapper(request, instance, loadBalancer);
        if (transformers != nu11) {
            for(LoadBalancerRequestTransformer transformer :
                    transformers) {
                serviceRequest =
                        transformer . transformRequest(serviceRequest,
                            instance);
            }
        }
        return execution. execute (serviceRequest, body) ;
    }
};

}
createRequest 中通过 ServiceRequestWrapper 来执行替换 URI 的逻辑,ServiceRequest Wrapper 中将 URI 的获取交给了 org.springframework.cloud.client.loadbalancer.LoadBalancer Client#reconstructURI 办法。

以上就是整个 RestTemplate 联合 @LoadBalanced 的执行流程,至于具体的实现大家能够本人去钻研,这里只是介绍原理及整个流程。

四、Ribbon API 应用
当你有一些非凡的需要,想通过 Ribbon 获取对应的服务信息时,能够应用 LoadBalancer Client 来获取,比方你想获取一个 fsh-house 服务的服务地址,能够通过 LoadBalancerClient 的 choose 办法来抉择一个:

@Autowiredprivate LoadBalancerClient loadBalancer;@GetMapping(“/choose”)public object chooseUrl() {

ServiceInstance instance = loadBalancer.choose("fsh-house");
return instance;

}
拜访接口,能够看到返回的信息如下:

{

serviceId: "fsh-house",
server: {
    host: "localhost",
    port: 8081,
    id: "localhost:8081",
    zone: "UNKNOWN",
    readyToServe: true,
    alive: true,
    hostPort: "localhost:8081",
    metaInfo: {
        serverGroup: null,
        serviceIdForDiscovery: null,
        instanceId: "localhost:8081",
        appName: null
    }
},
secure: false,
metadata:
{ },
host: "localhost",
port: 8081,
uri: "

}
五、Ribbon 饥饿加载
笔者从网上看到很多博客中都提到过的一个状况:在进行服务调用的时候,如果网络状况不好,第一次调用会超时。有很多大神对此提出了解决方案,比方把超时工夫改长一点、禁用超时等。Spring Cloud 目前正在高速倒退中,版本更新很快,咱们能发现的问题基本上等新版本进去的时候就都修复了,或者提供了最优的解决方案。

这个超时的问题也是 - 样,Ribbon 的客户端是在第一次申请的时候初始化的,如果超时工夫比拟短的话,初始化 Client 的工夫再加上申请接口的工夫,就会导致第一次申请超时。

通过配置 eager-load 来提前初始化客户端就能够解决这个问题。

ribbon.eager-load.enabled = true ribbon
eager-load.clients = fsh-house
ribbon.eager-load.enabled:开启 Ribbon 的饥饿加载模式。
ribbon.eager-load.clients:指定须要饥饿加载的服务名,也就是你须要调用的服务,若有多个则用逗号隔开。

退出移动版