In Nacos, service invocation is done primarily through RestTemplate + Ribbon, the Spring Restful request implementation class, and Ribbon is the client load balancer. You can use the Ribbon to obtain the service instance information (IP address and port number), and then use the RestTemplate to add the service instance information to complete a service invocation.

The RestTemplate + Ribbon can invoke services in two ways: through code and through annotations. But there are two ways to do itThe principle is the same: pull the list of available services to the local (client) through the registry, get the details of a server through the client load balancer, and then request the server, as shown in the figure below:

1. Call in code mode

Calling a service through code is not often used in practice, mainly because it is too cumbersome to write, but it is very helpful to understand the annotation call later, so we focus on here. A service invocation requires two roles: Provider and Consumer. Let’s create these two roles.

1.1 Creating a Service Provider: Provider

Step 1: Create a Spring Boot project (The Spring Cloud project is created based on Spring Boot), add spring-Web and Nacos-Discovery dependencies, the specific dependency information is as follows:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<! -- Add Nacos support -->
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
Copy the code

Step 2: Set the nacOS-related configuration. Add the following configuration to application.yml:

spring:
  application:
    name: springcloud-nacos-provider Project name (nacOS registered service name)
  cloud:
    nacos:
      discovery:
        username: nacos # nacos login user name
        password: nacos666 # nacos password
        server-addr: 127.0. 01.: 8848 # nacos server address
server:
  port: 8081 # project startup port number
Copy the code

Step 3: Add the service method as shown in the following code:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class HttpProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(HttpProviderApplication.class, args);
    }

    /** * provides a callable interface for the client */
    @RequestMapping("/call/{name}")
    public String call(@PathVariable String name) {
        return "I'm Provider. Received a message from: "+ name; }}Copy the code

Then use the same method to create two service providers with corresponding port numbers respectively:

127.0.0.1:8081

127.0.0.1:8082

127.0.0.1:8083

The contents of the three service providers are I’m Provider… , “I ‘m Provider2…” , “I ‘m Provider3…” , as shown in the figure below:

1.2 Creating a service caller: Consumer

At the heart of this article is the implementation code for the service caller, which is created in a similar way to the service provider. Step 1: Create a Spring Boot project and add the spring-Web and nacos-Discovery dependencies as follows:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<! -- Add Nacos support -->
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
Copy the code

Some people may wonder, the title of this article is Spring Cloud Alibaba Nacos + Ribbon, thatWhy not add a dependency on the Ribbon?This is aThe Ribbon framework is already built into Spring Cloud Alibaba NacosOpen the dependency tree of the project and you can see it clearly, as shown in the figure below: The second step: Set the nacOS-related configuration. Add the following configuration to application.yml:

spring:
  application:
    name: springcloud-nacos-consumer Project name (nacOS registered service name)
  cloud:
    nacos:
      discovery:
        username: nacos # nacos login user name
        password: nacos666 # nacos password
        server-addr: 127.0. 01.: 8848 # nacos server address
server:
  port: 8091 # project startup port number
Copy the code

Step 3: In the project startup class, declare the RestTemplate object using Spring Java Config as shown in the following code:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class RibbonCodeConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(RibbonCodeConsumerApplication.class, args);
    }

    /** * Declare RestTemplate */ using the Spring Java Config method
    @Bean
    RestTemplate restTemplate(a) {
        return newRestTemplate(); }}Copy the code

Step 4: Use the RestTemplate + Ribbon code to invoke a service. First, use the Choose method of the LoadBalancerClient object provided by the Ribbon to obtain a healthy service instance based on the service ID in Nacos. The service instance contains the IP address and port number of the service. Then use the RestTemplate to access the service based on the obtained IP address and port number.

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;

@RestController
public class ConsumerController {
    // Load balancing objects provided by the Ribbon
    @Resource
    private LoadBalancerClient loadBalancerClient;

    // Spring provides Restful request objects
    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer")
    public String consumer(@RequestParam String name) {
        // Get the service instance based on the object + Nacos service ID provided by the Ribbon
        ServiceInstance serviceInstance = loadBalancerClient.choose("springcloud-nacos-provider");
        // Get the IP address of the service instance
        String ip = serviceInstance.getHost();
        // Get the port number in the service instance
        int port = serviceInstance.getPort();
        // Use restTemplate to request and get the result
        String result = restTemplate.getForObject("http://" + ip + ":" + port + "/call/" + name,String.class);
        returnresult; }}Copy the code

The execution result of the above program is as follows:

2. Annotation invocation

The service provider is created in the same way as above, so let’s create an annotated service caller, Consumer. Step 1: Create a Spring Boot project and add the spring-Web and nacos-Discovery dependencies as follows:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<! -- Add Nacos support -->
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
Copy the code

Step 2: Set the nacOS-related configuration. Add the following configuration to application.yml:

spring:
  application:
    name: springcloud-nacos-consumer Project name (nacOS registered service name)
  cloud:
    nacos:
      discovery:
        username: nacos # nacos login user name
        password: nacos666 # nacos password
        server-addr: 127.0. 01.: 8848 # nacos server address
server:
  port: 8092 # project startup port number
Copy the code

Step 3: In the project startup class, declare the RestTemplate object using Spring Java Config. In this step, add the @loadBalanced annotation to the RestTemplate object. Add this annotation to make the RestTemplate object automatically support load balancing, as shown in the following code:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class RibbonAnnotationConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(RibbonAnnotationConsumerApplication.class, args);
    }

    @LoadBalanced // Enable the RestTemplate to automatically support Ribbon load balancing
    @Bean
    public RestTemplate restTemplate(a) {
        return newRestTemplate(); }}Copy the code

Step 4: Create a client request method, the specific implementation code is as follows:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RestController
public class ConsumerController {
    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer")
    public String consumer(@RequestParam String name) {
        // Request and obtain the result (SpringCloud-nacos-provider is nacOS service ID)
        String result = restTemplate.getForObject("http://springcloud-nacos-provider/call/" + name, String.class);
        returnresult; }}Copy the code

The execution result of the above program is as follows:

Annotation implementation principle analysis

The key to Nacos is to use @loadBalanced, which gives the RestTemplate load balancing capability to correctly call the service. You know the answer to this question, you must read LoadBalancerAutoConfiguration source. LoadBalancerAutoConfiguration is to implement the client load balancer automatic assembly, as the start and the start of Spring, its source content has a lot of, we here to capture part of the core method to take a look at:

@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
    return () -> {
        restTemplateCustomizers.ifAvailable((customizers) -> {
            Iterator var2 = this.restTemplates.iterator();

            while(var2.hasNext()) {
                RestTemplate restTemplate = (RestTemplate)var2.next();
                Iterator var4 = customizers.iterator();

                while(var4.hasNext()) { RestTemplateCustomizer customizer = (RestTemplateCustomizer)var4.next(); customizer.customize(restTemplate); }}}); }; }Copy the code

Here enclosing restTemplates. The iterator () has all been @ LoadBalanced annotations modified RestTemplate object, All RestTemplate objects modified by @loadBalanced are forcefully converted to RestTemplateCustomizer objects.

@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
    return (restTemplate) -> {
        List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors());
        list.add(loadBalancerInterceptor);
        restTemplate.setInterceptors(list);
    };
}
Copy the code

All RestTemplate objects modified by the @loadBalanced annotation add a loadBalancerInterceptor interceptor to them.

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) {
        this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
    }

    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException { URI originalUri = request.getURI(); String serviceName = originalUri.getHost(); Assert.state(serviceName ! =null."Request URI does not contain a valid hostname: " + originalUri);
        return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution)); }}Copy the code

A LoadBalanced RestTemplate object, which is modified by the @LoadBalanced annotation, is intercepted by the LoadBalancerInterceptor. After interception, the LoadBalancerClient object is used to obtain a healthy service instance according to the load balancing policy, and then invoke the instance method through the IP address and port of the service instance to complete the service request.

conclusion

Nacos invokes Restful services through the built-in Ribbon framework and has two ways to invoke them, either through code or through annotations. The annotation method is easier to use. Simply add a @loadBalanced annotation to the RestTemplate object to enable load balancing for the request object.

Judge right and wrong from yourself, praise to listen to others, gain and loss in the number.

Public account: Java Chinese Community

Java Interview Collection: gitee.com/mydb/interv…