How to use resttemplate with ribbon?

Time:2021-8-24

1、 Integrating ribbon with resttemplate
Spring provides a simple and convenient template class for API calls, that is, resttemplate.

  1. Using resttemplate
    First, let’s take a look at the use of get requests: two interfaces are added to the housecontroller of FSH house service. One passes parameters through @ requestparam and returns an object information; The other passes parameters through @ pathvariable and returns a string. Try to assemble different forms through two interfaces, as shown in the following code.

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

Return new houseinfo (1L, "Shanghai", "Hongkou", "dongti community");

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

return name;

}
In the FSH substitution service, use resttemplate to call the two interfaces we just defined, as shown in the following code.

@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);

}
The data results are obtained through the getforobject method of resttemplate (as shown in the following code). This method has three overloaded implementations:

URL: the API address of the request. There are two ways, one is in the form of string and the other is in the form of URL.
Responsetype: type of return value.
Urivariables: pathvariable parameter. There are two methods, one is variable parameter and the other is 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) ;
In addition to getforobject, we can also use getforentity to obtain data, as shown in the following code.

@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 can get the returned status code, request header and other information, and get the content of the response through getbody. The rest, like getforobject, have three overloaded implementations.

Next, let’s see how to use post to call the interface. Add a save method in the housecontroller to receive houseinfo data, as shown in the following code.

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

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

}
Then write the calling code and call it with postforobject, as shown in the following code.

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

HouseInfo houseInfo = new HouseInfo();
Houseinfo.setcity ("Shanghai");
Houseinfo.setregion ("Hongkou");
houseInfo.setName( "XXX");
Long id = restTemplate.postFor0bject(
    "http: //1ocalhost:8081/ house/save",houseInfo,Long.class);
return id;

}
Postforobject also has three overloaded implementations. In addition to postforebject, you can also use the postforentity method. The usage is the same, as shown in the following code.

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);
In addition to the methods corresponding to get and post, resttemplate also provides put, delete and other operation methods. Another practical method is the exchange method. Exchange can execute four request modes: get, post, put and delete.

  1. Integrate ribbon
    To integrate the ribbon in the spring cloud project, you only need to add the following dependencies in pom.xml. In fact, you don’t need to configure it, because the ribbon has been referenced in Eureka, as shown in the following code.

<dependency>

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

</dependency>
This configuration is added to Fangjia FSH substitution service.

2、 Resttemplate load balancing example
Make some small modifications to the previous code and output some contents to prove that our integrated ribbon is effective.

Transform the Hello interface and output the port of the current service in the interface to distinguish the called services, as shown in the following code.

@[email protected](“/house” )public class HouseController {

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

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

}
The above code starts two services on ports 8081 and 8083 respectively, which will be used later.

Then modify the code of the callhello interface and output the call result to the console, as shown in the following code.

@[email protected](“/substitution”)public class Substitut ionController{

@Autowired
private RestTemplate restTemplate;
@GetMapping ("/cal1Hel1o")
public String cal1Hello(){
    String result = restTemplate. getFor0bject(
        ");
    System. Out. Print1n ("call result:" + result);
    return result ;
}

}
The test steps are as follows:

Restart the service.
Access interface.
Check the console output to see if the load is working.

3、 @ loadbalanced annotation principle
I believe you must have a question: why can the resttemplate be combined with Eureka after adding @ loadbalanced to the resttemplate? You can use the service name to call the interface and load balance?

This credit should be attributed to the spring cloud, which has done us a lot of bottom work, because it encapsulates all these, so it will be so simple for us to use. Framework is to simplify code and improve efficiency.

The main logic is to add an interceptor to the resttemplate to replace the requested address before the request, or select the service address according to the specific load policy and then call it. This is the principle of @ loadbalanced.

Let’s implement a simple interceptor to see if it will enter the interceptor before calling the interface. We don’t do anything, just output a sentence to prove that we can come in. As shown in the following code.

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 ("enter the custom request interceptor" + servicename);
    return execution.execute(request, body);
}

}
After the interceptor is ready, we can define an annotation, copy the @ loadbalanced code and change the name, as shown in the following code.

@Target({ ElementType.FIELD,ElementType.PARAMETER,ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface MyLoadBalanced {
}
Then define a configuration class to inject the resttemplate into the interceptor, as shown in the following code.

@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);
            }
        }
    };
}

}
Maintain a resttemplate list of @ myloadbalanced, and set the interceptor for resttemplate in smartlogicalizing singleton.

Then modify our previous resttemplate configuration and change @ loadbalanced to our custom @ myloadbalanced, as shown in the following code.

@Bean//@[email protected] RestTemplate getRestTemplate(){

return new RestTemplate() ;

}
After restarting the service, you can see the output of the console. This proves that the interceptor will enter when the interface is called. The output is as follows:

Through this small case, we can clearly understand the working principle of @ loadbalanced. Next, let’s look at the logic in the source code.

First, let’s look at the configuration class and how to set interceptors for resttemplate. The code is in the orgspringframework.cloud.client.loadbalancer.loadbalancenautoconfiguration class in spring – cloud commonsjar by viewing the loadbalancenautoconfigurationWebpage Game+From the source code, you can see that a @ loadbalanced resttemplate list is also maintained here, as shown in the following code.

@[email protected](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);
            
            }
        }
    }
};

}
Let’s take a look at the configuration of the interceptor. As you can see, the interceptor uses the loadbalancerinterceptor, and the resttemplate customizer is used to add the interceptor, as shown in the following code.

@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);
        }
    };
}

}
The interceptor code is in org.www.sangpi.comspringframework.cloud.client.loadbalancerloadbalancerinceptor, as shown in the following code.

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));
}

}
The main logic is in intercept. The execution is handed over to loadbalancerclient for processing. A loadbalancerrequest object is built through loadbalancer requestfactory, as shown in the following code.

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) ;
    }
};

}
In createrequest, the logic of replacing URI is executed through servicerequestwrapper, which gives the URI acquisition to the org.springframework.cloud.client.loadbalancer.loadbalancer client#reconstructuri method.

The above is the execution process of the whole resttemplate combined with @ loadbalanced. As for the specific implementation, you can study it yourself. Here is only the principle and the whole process.

4、 Ribbon API usage
When you have some special needs and want to obtain the corresponding service information through the ribbon, you can use the loadbalancer client. For example, if you want to obtain the service address of a FSH house service, you can select one through the choose method of loadbalancerclient:

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

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

}
Access the interface, you can see the returned information as follows:

{

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: "

}
5、 Ribbon hungry loading
The author has seen a situation mentioned in many blogs on the Internet: when calling a service, if the network situation is bad, the first call will timeout. Many great gods have proposed solutions to this, such as changing the timeout to longer, disabling timeout, etc. Spring cloud is currently developing at a high speed, and the version is updated very quickly. Basically, the problems we can find will be repaired when the new version comes out, or the best solution is provided.

The timeout problem is also – like. The ribbon client is initialized at the time of the first request. If the timeout time is relatively short, the time of initializing the client plus the time of requesting the interface will lead to the timeout of the first request.

This problem can be solved by configuring eager load to initialize the client in advance.

ribbon.eager-load.enabled = true ribbon
eager-load.clients = fsh-house
Ribbon.eager-load.enabled: enable the hungry loading mode of ribbon.
Ribbon.eager-load.clients: specify the name of the service to be loaded, that is, the service you need to call. If there are multiple, separate them with commas.