1. Using RestTemplate and Integrating Ribbon
Spring provides a simple and convenient template class for API calls, RestTemplate.
- Use RestTemplate
First, let's look at how GET requests are used: add two interfaces to the HouseController of the fsh-house service, one that passes parameters through @RequestParam and returns an object information; The other passes an argument through @PathVarable and returns a string. Try to assemble different forms through two interfaces, as shown in the code below.
@GetMapping("/data")public HouseInfo getData( @RequestParam("name") String name) {
return new HouseInfo(1L, "Shanghai","Iris","Eastern Community");
}
@GetMapping("/data/{name}")
public String getData2(@PathVariable( "name") String name) {
return name;
}
Use RestTemplate in the fsh-substitution service to call the two interfaces we just defined, as shown in the code below.
@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);
}
Getting data results is achieved through the RestTemplate getForObject method (shown in the code below), which has three overloaded implementations:
URL: There are two ways to request an API address, one as a string and the other as a URL.
responseType: The type of return value.
UriVariables: The PathVariable parameter can be used in two ways, one as a variable parameter and the other as a 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 get data, as shown in the code below.
@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 ;
}
The status code returned, the request header, and so on, can be obtained in getForEntity, and the content of the response can be obtained through getBody. The rest, like getForObject, have three overloaded implementations.
Next, see how to invoke the interface using POST. Add a save method to the HouseController to receive HouseInfo data, as shown in the code below.
@PostMapping("/save")public Long addData(@RequestBody HouseInfo houseInfo) {
System.out.println(houseInfo. getName()); return 1001L;
}
Next, write the calling code and call it with postForObject, as shown below.
@GetMapping("/save")public Long add(){
HouseInfo houseInfo = new HouseInfo(); houseInfo.setCity("Shanghai"); houseInfo.setRegion("Iris"); houseInfo.setName( "XXX"); Long id = restTemplate.postFor0bject( "http: //1ocalhost:8081/ house/save",houseInfo,Long.class); return id;
}
PosForObject also has three overloaded implementations. In addition to postForObject, you can also use the postForEntity method, as shown in the code below.
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 get and post methods, RestTemplate provides put, delete, and so on. Another practical method is the exchange method. Exchange can execute get, post, put, delete.
- Integrate Ribbon
Integrating Ribbon in the Spring Cloud project only requires a pom. You can add the following dependencies to the xml, or you can actually not configure them, because Ribbon is already referenced in Eureka, as shown in the code below.
<dependency>
<groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
This configuration is added to the fangjia-fsh-substitution-service.
2. RestTemplate Load Balancing Example
Make small changes to the previous code and output some content to prove that our integrated Ribbon is valid.
Modify the hello interface to output the port of the current service to distinguish the invoked service, as shown in the code below.
@RestController@RequestMapping("/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 you will use later.
The code for the callHello interface is then modified to output the call results to the console, as shown in the code below.
@RestController@RequestMapping("/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.
Looking at the console output, you know if the load is working.
3. @LoadBalanced Annotation Principle
I'm sure there must be a question: Why can RestTemplate be combined with Eureka after adding @LoadBalanced to RestTemplate, use service names to invoke interfaces, and load balancing?
This is due to Spring Cloud doing a lot of low-level work for us, because it packages all of these and makes it so easy for us to use. Framework is created to simplify code and improve efficiency.
The main logic is to add an interceptor to RestTemplate, replace the requested address before the request, or select the service address based on the specific load policy and call it again, which is how @LoadBalanced works.
Let's implement a simple interceptor and see if it will enter before calling the interface. We don't do anything, just output a sentence to prove we can come in. As shown in the code below.
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 a custom request interceptor"+serviceName); return execution.execute(request, body); }
}
Once the interceptor is ready, we define a comment to copy the @LoadBalanced code and rename it as shown below.
@Target({ ElementType.FIELD,ElementType.PARAMETER,ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface MyLoadBalanced {
}
Then define a configuration class to annotate the RestTemplate human interceptor, as shown in the code below.
@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 list of RestTemplate for @MyLoadBalanced and intercept RestTemplate in SmartlnitializingSingleton.
Then transform our previous ReestTemplate configuration and change @LoadBalanced to our custom @MyLoadBalanced, as shown in the code below.
@Bean//@LoadBalanced@MyLoadBalancedpublic RestTemplate getRestTemplate(){
return new RestTemplate() ;
}
Restart the service, access can see the console output, which proves that the interceptor will enter when the interface is called, the output is as follows:
In this small case, we can clearly see how @LoadBalanced works. Next let's see what kind of logic is in the source code.
First look at the configuration class, how to set the interceptor for RestTemplate, the orgspringframework in the spring-cloud commonsjar code. Cloud. Client. Loadbalancer. Inside the LoadBalancerAutoConfiguration class by viewing the Webpage Game +Source, as you can see here is also a list of estTemplates maintained for @LoadBalanced, as shown in the code below.
@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); } } } };
}
Let's look at the configuration of the interceptor. As you know, the interceptor uses LoadBalancerInterceptor, and RestTemplate Customizer adds the interceptor, as shown in the code below.
@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 at org. Www.sangpi. Comspringframework. Cloud. Client. In the loadbalancerLoadBalancerInterceptor, as shown in the code below.
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 in intercept is that execution is handled by the LoadBalancerClient, and a LoadBalancerRequest object is built from the LoadBalancer RequestFactory, as shown in the code below.
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) ; } };
}
The logic for replacing URIs is implemented through ServiceRequestWrapper in createRequest, which gives the URI acquisition to org.springframework.cloud.client.loadbalancer.LoadBalancer Client#reconstructURI method.
This is the entire RestTemplate implementation process combined with @LoadBalanced. As for the specific implementation, you can study it yourself, here is just the introduction of the principle and the entire process.
IV. Ribbon API usage
LoadBalancer Client can be used to obtain service information when you have some special needs and want to get the corresponding service information through Ribbon. For example, if you want to get a service address of an fsh-house service, you can select one by choose method of LoadBalancer Client:
@Autowiredprivate LoadBalancerClient loadBalancer;@GetMapping("/choose")public object chooseUrl() {
ServiceInstance instance = loadBalancer.choose("fsh-house"); return instance;
}
Access the interface and you can see that the information returned is 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 Hunger Loading
The author sees a situation mentioned in many blogs on the Internet: when making service calls, if the network is not in good condition, the first call will time out. There are many gods who have come up with solutions to this, such as making the timeout longer, and disabling it. Spring Cloud is currently growing at a high speed, with versions updated very quickly, and the problems we can find will basically be fixed when the new version comes out, or provide the best solution.
The problem with this timeout is also-like, Ribbon's Client is initialized on the first request, and if the timeout is short, the time to initialize the Client plus the time to request the interface will cause the first request to time out.
This can be solved by configuring eager-load to initialize the client ahead of time.
ribbon.eager-load.enabled = true ribbon
eager-load.clients = fsh-house
ribbon.eager-load.enabled: Turn on Ribbon's starvation loading mode.
ribbon.eager-load.clients: Specify the name of the service that needs to be loaded hungrily, that is, the service you need to call, separated by commas if there are more than one.