Dubbo: Integrated Spring Cloud

Keywords: Dubbo Spring Java REST

1. Overview

In this article, let's share Spring Cloud Alibaba Dubbo Source Parsing for the project to see how Dubbo is integrated into Spring Cloud.

_Spring Cloud and Dubbo have never been competitive, so fat friends need to understand them well.

Spring Cloud Alibaba Dubbo currently has no documentation, but don't worry, Sweet will teach you to build an example first, and it's also an example we'll use to debug later.

2. Debugging environment setup

Before reading the source code, of course, we set up the debugging environment.

2.1 Dependent Tools

  • JDK : 1.8+
  • Maven
  • IntelliJ IDEA

2.2 Source Pull

From the official warehouse https://github.com/spring-cloud-incubator/spring-cloud-alibaba Fork belongs to its own warehouse.Why Fork?Now that we start reading and debugging the source code, we may write some comments and have our own repository where we can submit freely._

Use IntelliJ IDEA to pull the replacement code from Fork's warehouse.Once the pull is complete, Maven downloads the dependent packages and may take some time to wait.

For convenience, we directly use the example provided by the spring-cloud-alibaba-dubbo project, which is located in its test test test directory, as shown in the following figure:

Additionally, the spring-cloud-alibaba version used in this article is 0.2.2.BUILD-SNAPSHOT.

2.3 Start Nacos

Since we'll use Nacos as the registry and configuration center later, we need to start it.Specific, refer to the genitals in "Nacos Implementation Principles and Source Parsing System - Collection of Excellent Works" Of "2. Quick Start" Subsection.

2.4 Startup Example

Right-click the #main(String[] args) method of the DubboSpringCloudBootstrap class and run it directly.If you do not see any abnormal output, you are already successful.

In addition, this example is a service consumer and a service provider.

Next, let's step by step explain each class and configuration file in the example.

2.4.1 bootstrap.yaml

spring:
  application:
    name: spring-cloud-alibaba-dubbo
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
      config:
        server-addr: 127.0.0.1:8848

eureka:
  client:
    enabled: false

---
spring:
  profiles: eureka
  cloud:
    nacos:
      discovery:
        enabled: false
        register-enabled: false

eureka:
  client:
    enabled: true
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka/
  • Eureka-related configurations can be ignored because we are using Nacos as the registry.
  • spring.application.name, with an application name configured.
  • spring.cloud.nacos.discovery.server-addr, which is configured with Nacos as the registry.
  • spring.cloud.nacos.config.server-addr, which configures Nacos as the configuration center.

2.4.2 application.yaml

dubbo:
  scan:
    base-packages: org.springframework.cloud.alibaba.dubbo.service # Scan the specified package to generate corresponding @Service and @Reference Bean objects
  protocols:
    dubbo:
      name: dubbo # Dubbo Protocol
      port: 12345 # Ports for the Dubbo protocol
    rest:
      name: rest # REST Protocol
      port: 9090 # REST Protocol Port
      server: netty # Use Netty as HTTP Server
  registry:
    address: spring-cloud://nacos # Dubbo Registry

feign:
  hystrix:
    enabled: true # Turn on Hystrix to break

server:
  port: 8080 # HTTP API Port
  • For each configuration, look at the configuration files that follow.

2.4.3 EchoService

org.springframework.cloud.alibaba.dubbo.service.EchoService, EchoService interface.The code is as follows:

// EchoService.java

public interface EchoService {

    String echo(String message);

    String plus(int a, int b);

}
  • Familiarize yourself with code that is not familiar with the Dubbo Service interface~

2.4.4 DefaultEchoService

org.springframework.cloud.alibaba.dubbo.service.DefaultEchoService, implements the EchoService interface, default EchoService implementer, and service provider.The code is as follows:

// DefaultEchoService.java

@Service(version = "1.0.0", protocol = {"dubbo", "rest"})
@RestController
@Path("/")
public class DefaultEchoService implements EchoService {

    @Override
    @GetMapping(value = "/echo"
//            consumes = MediaType.APPLICATION_JSON_VALUE,
//            produces = MediaType.APPLICATION_JSON_UTF8_VALUE
    )
    @Path("/echo")
    @GET
//    @Consumes("application/json")
//    @Produces("application/json;charset=UTF-8")
    public String echo(@RequestParam @QueryParam("message") String message) {
        System.out.println(message);
        return RpcContext.getContext().getUrl() + " [echo] : " + message;
    }

    @Override
    @PostMapping("/plus")
    @Path("/plus")
    @POST
    public String plus(@RequestParam @QueryParam("a") int a, @RequestParam @QueryParam("b") int b) {
        return null;
    }

}
  • The @Service(version = "1.0.0", protocol = {"dubbo", "rest"}) annotation provides services for both the Dubbo and Rest protocols.

2.4.5 DubboSpringCloudBootstrap

org.springframework.cloud.alibaba.dubbo.bootstrap.DubboSpringCloudBootstrap, sample Spring Boot launcher.The code is as follows:

// DubboSpringCloudBootstrap.java

@EnableDiscoveryClient // Open Registration Discovery
@EnableAutoConfiguration // Turn on automatic configuration
@EnableFeignClients // Open Feign Client
@RestController
public class DubboSpringCloudBootstrap {

    @Reference(version = "1.0.0")
    private EchoService echoService;

    @Autowired
    @Lazy
    private FeignEchoService feignEchoService;

    @Autowired
    @Lazy
    private DubboFeignEchoService dubboFeignEchoService;

    public static void main(String[] args) {
        new SpringApplicationBuilder(DubboSpringCloudBootstrap.class)
                .run(args);
    }

}
  • The @EnableDiscoveryClient annotation, which opens the ability to register discovery Clients.
  • The @EnableAutoConfiguration annotation, used to turn on automatic configuration.
  • @EnableFeignClients annotation, open Feign Client.In the spring-cloud-alibaba-dubbo project, Feign is used as the consumer of services.
  • The @RestController annotation, which we will see later, provides an HTTP API interface based on Spring MVC.
  • echoService property, using the @Reference(version = "1.0.0") annotation, introduces the Dubbo service.This is the way we used it in Dubbo.
  • The feignEchoService property, which uses the standard Feign Client as the service consumer, calls the Rest interface provided by Dubbo using RestTemplate.The code is as follows:

    // DubboSpringCloudBootstrap.java
    
    @FeignClient("spring-cloud-alibaba-dubbo")
    public interface FeignEchoService {
    
        @GetMapping(value = "/echo")
        String echo(@RequestParam("message") String message);
    
    }
    
  • The dubboFeignEchoService property also uses the standard Feign Client as the service consumer, which calls the Dubbo interface provided by Dubbo.The code is as follows:

    // DubboSpringCloudBootstrap.java
    
    @FeignClient("spring-cloud-alibaba-dubbo")
    public interface DubboFeignEchoService {
    
        @GetMapping(value = "/echo")
        @DubboTransported
        String echo(@RequestParam("message") String message);
    
    }
    
    • Compared to the echoService property, it adds the annotation org.springframework.cloud.alibaba.dubbo.annotation.@DubboTransported to the method.
    • There may be fat friendliness, how exactly is it achieved?Let's unveil ~
// DubboSpringCloudBootstrap.java

@Bean
public ApplicationRunner applicationRunner() {
    return arguments -> {
        // Dubbo Service call
        System.out.println(echoService.echo("mercyblitz"));
        // Spring Cloud Open Feign REST Call
        System.out.println(feignEchoService.echo("mercyblitz"));
        // Spring Cloud Open Feign REST Call (Dubbo Transported)
        System.out.println(dubboFeignEchoService.echo("mercyblitz"));
    };
}
  • The ApplicationRunner Bean object is declared, and the Spring Boot startup completes, directly launching the corresponding call.Its purpose is to see if all three methods of invocation work.If there are no errors, the instructions are OK.
// DubboSpringCloudBootstrap.java

@GetMapping(value = "/dubbo/call/echo")
public String dubboEcho(@RequestParam("message") String message) {
    return echoService.echo(message);
}

@GetMapping(value = "/feign/call/echo")
public String feignEcho(@RequestParam("message") String message) {
    return feignEchoService.echo(message);
}

@GetMapping(value = "/feign-dubbo/call/echo")
public String feignDubboEcho(@RequestParam("message") String message) {
    return dubboFeignEchoService.echo(message);
}
  • Three Spring MVC HTTP API s are declared, calling three different service consumers.
  • This is why DubboSpringCloudBootstrap has the @RestController annotation and server.port=8080 is configured in the configuration file.

So far, we've taken a complete look at the example of Spring Cloud Alibaba Dubbo and are ready to start debugging happily.

3. Overview of project structure

This article mainly shares the project structure of spring-cloud-alibaba-dubbo.
I hope this article can give fat friends a simple understanding of the overall project of spring-cloud-alibaba-dubbo.

3.1 Code Statistics

Here's a quick tip to share.When starting source code learning, the author will first understand the amount of code in the project.

First, use IDEA Statistic Plug-in to count the total code volume.

  • As we can see roughly, the entire Spring Cloud Alibaba has 16,000 lines of code.It also includes unit tests, samples, and so on.
  • /(o)/~~Obviously, there is no easy way for this plugin to figure out how much code we want to see in spring-cloud-alibaba-dubbo at the moment.

Second, use Shell script commands count Maven module by module .

Typically, the author uses find. -name'*.java'| xargs cat | grep-v-e ^$-e ^\s*\//. *$|wc-l.This command filters only some of the comments, so compare IDEA Statistic More.

Of course, for accuracy, fat friends need to manually cd to the src/main/java directory of each Maven project to achieve the amount of code that excludes unit tests.

After counting, we found that there was not much code.

3.2 annotation package

Annotation package, 62 lines of code, with @EnableFeignClients annotation.

3.3 autoconfigure package

The autoconfigure package, 172 lines of code, provides Spring Boot auto-configuration for Spring Cloud Alibaba Dubbo.

3.4 context package

context package, 35 lines of code, currently only has the DubboServiceRegistrationApplicationContextInitializer class, do not explain Ha~

3.5 metadata package

The metadata package, 904 lines of code, implements metadata for services from Spring Cloud Alibaba Dubbo, stored in the configuration center.This way, subsequent Dubbo calls with the @DubboTransported annotation will be used because Generalized calls to Dubbo With metadata for services, you can make pleasant calls.

3.6 openfeign package

The openfeign package, 305 lines of code, integrates Dubbo into OpenFeign.

3.7 Regisry Package

The registry package, 478 lines of code, implements that Dubbo uses the Spring Cloud Service registry system.Maybe that's a bit abstract. We can look back. 「2.4.2 application.yaml」 You can see that there is a magical dubbo.protocols.registry.address=spring-cloud://nacos configuration.

emm~hahaha, wait to see the specific code, it will be clearer.

4. annotation package

4.1 @DubboTransported

The org.springframework.cloud.alibaba.dubbo.annotation.@DubboTransported annotation uses Dubbo as the underlying RPC call when the table name is called.The code is as follows:

// DubboTransported.java

/**
 * {@link DubboTransported @DubboTransported} annotation indicates that the traditional Spring Cloud Service-to-Service call is transported
 * by Dubbo under the hood, there are two main scenarios:
 * <ol>
 * <li>{@link FeignClient @FeignClient} annotated classes:
 * <ul>
 * If {@link DubboTransported @DubboTransported} annotated classes, the invocation of all methods of
 * {@link FeignClient @FeignClient} annotated classes.
 * </ul>
 * <ul>
 * If {@link DubboTransported @DubboTransported} annotated methods of {@link FeignClient @FeignClient} annotated classes.
 * </ul>
 * </li>
 * <li>{@link LoadBalanced @LoadBalanced} {@link RestTemplate} annotated field, method and parameters</li>
 * </ol>
 * <p>
 *
 * @see FeignClient
 * @see LoadBalanced
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) // Supporting classes, methods
@Documented
public @interface DubboTransported {

    /**
     * The protocol of Dubbo transport whose value could be used the placeholder "dubbo.transport.protocol"
     *
     * The Dubbo protocol used, defaulted to "dubbo"
     *
     * @return the default protocol is "dubbo"
     */
    String protocol() default "${dubbo.transport.protocol:dubbo}";

    /**
     * The cluster of Dubbo transport whose value could be used the placeholder "dubbo.transport.cluster"
     *
     * Cluster fault tolerance used, default is "failover"
     *
     * @return the default protocol is "failover"
     */
    String cluster() default "${dubbo.transport.cluster:failover}";

}
  • Protocol property, using the Dubbo protocol, defaults to "dubbo".
  • The cluster property, which uses the cluster fault tolerance method used, defaults to "failover".
  • Can be marked on a class or method.

5. autoconfigure package

Three automatic configuration classes are declared in the META-INF/spring.factories file.The code is as follows:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboMetadataAutoConfiguration,\
  org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboOpenFeignAutoConfiguration,\
  org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboRestMetadataRegistrationAutoConfiguration
  • Next, let's take a look one by one.

5.1 DubboMetadataAutoConfiguration

Org.springframework.cloud.alibaba.dubbo.autoconfigure.Dubbo Metadata AutoConfiguration, an automatic configuration class for Dubbo metadata-related beans.The code is as follows:

// DubboMetadataAutoConfiguration.java

@Configuration
@Import(DubboServiceMetadataRepository.class) // Created the DubboServiceMetadataRepository Bean object
public class DubboMetadataAutoConfiguration {

    @Bean // Created NacosMetadataConfigService Bean object
    @ConditionalOnBean(NacosConfigProperties.class)
    public MetadataConfigService metadataConfigService() {
        return new NacosMetadataConfigService();
    }

}

5.2 DubboOpenFeignAutoConfiguration

org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboOpenFeignAutoConfiguration, the automatic configuration class for Dubbo OpenFeign-related beans.The code is as follows:

// DubboOpenFeignAutoConfiguration.java

@ConditionalOnClass(value = Feign.class) // When a Feign class exists, there is a feign dependency
@AutoConfigureAfter(FeignAutoConfiguration.class) // Initialize after FeignAutoConfiguration configuration class
@Configuration
public class DubboOpenFeignAutoConfiguration {

    @Value("${spring.application.name}")
    private String currentApplicationName;

    @Bean // Create a DubboServiceBeanMetadataResolver object
    @ConditionalOnMissingBean
    public MetadataResolver metadataJsonResolver(ObjectProvider<Contract> contract) {
        return new DubboServiceBeanMetadataResolver(currentApplicationName, contract);
    }

    @Bean // Create TargeterBeanPostProcessor object
    public TargeterBeanPostProcessor targeterBeanPostProcessor(Environment environment,
                                                               DubboServiceMetadataRepository dubboServiceMetadataRepository) {
        return new TargeterBeanPostProcessor(environment, dubboServiceMetadataRepository);
    }

}
  • #metadataJsonResolver(...) method to create a DubboServiceBeanMetadataResolver Bean object.For a detailed analysis, see 「7.2 MetadataResolver」 .
  • #targeterBeanPostProcessor(...) method to create a TargeterBeanPostProcessor Bean object.For a detailed analysis, see 「8.1 TargeterBeanPostProcessor」.

5.3 DubboRestMetadataRegistrationAutoConfiguration

Org.springframework.cloud.alibaba.dubbo.autoconfigure.Dubbo Rest Metadata Registration AutoConfiguration, automatically configures two Spring event listeners to register Dubbo Rest metadata with the configuration center.The code is as follows:

// DubboRestMetadataRegistrationAutoConfiguration.java

@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true) // "spring.cloud.service-registry.auto-registration.enabled=true" is required or not configured.
@ConditionalOnBean(value = { // Require the existence of MetadataResolver, MetadataConfigService Bean objects
        MetadataResolver.class,
        MetadataConfigService.class
})
@AutoConfigureAfter(value = {DubboMetadataAutoConfiguration.class}) // Initialize after the DubboMetadataAutoConfiguration configuration class
@Configuration
public class DubboRestMetadataRegistrationAutoConfiguration {

    /**
     * A Map to store REST metadata temporary, its' key is the special service name for a Dubbo service,
     * the value is a JSON content of JAX-RS or Spring MVC REST metadata from the annotated methods.
     *
     * Dubbo Rest Service Metadata collection of methods
     */
    private final Set<ServiceRestMetadata> serviceRestMetadata = new LinkedHashSet<>();

    @Autowired // By default, the SubboServiceBeanMetadataResolver Bean object from the DubboOpenFeignAutoConfiguration registration
    private MetadataResolver metadataResolver;

    @Autowired // By default, the NacosMetadataConfigService Bean object from the DubboMetadataAutoConfiguration registration
    private MetadataConfigService metadataConfigService;

    @EventListener(ServiceBeanExportedEvent.class)
    public void recordRestMetadata(ServiceBeanExportedEvent event) throws JsonProcessingException {
        ServiceBean serviceBean = event.getServiceBean();
        serviceRestMetadata.addAll(metadataResolver.resolveServiceRestMetadata(serviceBean));
    }

    /**
     * Pre-handle Spring Cloud application service registered:
     * <p>
     * Put <code>restMetadata</code> with the JSON format into
     * {@link Registration#getMetadata() service instances' metadata}
     * <p>
     *
     * @param event {@link InstancePreRegisteredEvent} instance
     */
    @EventListener(InstancePreRegisteredEvent.class)
    public void registerRestMetadata(InstancePreRegisteredEvent event) throws Exception {
        Registration registration = event.getRegistration();
        metadataConfigService.publishServiceRestMetadata(registration.getServiceId(), serviceRestMetadata);
    }

}
  • serviceRestMetadata property, a collection of Metadata for the Dubbo Rest Service method.
  • #recordRestMetadata(ServiceBeanExportedEvent) method that listens for ServiceBeanExportedEvent events.
  • #registerRestMetadata(InstancePreRegisteredEvent) method that listens for InstancePreRegisteredEvent events.
    • The InstancePreRegisteredEvent event is triggered before the Spring Cloud application is registered with the registry.Detailed, you can see the <spring-cloud-commons reference> Of articles 「2.2.1 ServiceRegistry Auto-Registration」 Subsection.Now, you can skip ~
    • Receiving the InstancePreRegisteredEvent event, the method calls the MetadataConfigService#publishServiceRestMetadata (String serviceName, Set<ServiceRestMetadata> serviceRestMetadata) method to publish (store) the ServiceRestMetadata metadata collection of each Dubbo service to the configuration center.
  • This way, subsequent Ubbo generalization calls have metadata for the Dubbo service.

Because this section is all about automatic configuration classes, fat friends may be slightly confused.Don't panic. Let's move on.

Yes, it's best to watch while debugging.

6. context Package

Three automatic configuration classes are declared in the META-INF/spring.factories file.The code is as follows:

org.springframework.context.ApplicationContextInitializer=\
  org.springframework.cloud.alibaba.dubbo.context.DubboServiceRegistrationApplicationContextInitializer

6.1 DubboServiceRegistrationApplicationContextInitializer

org.springframework.cloud.alibaba.dubbo.context.DubboServiceRegistrationApplicationContextInitializer, implements the ApplicationContextInitializer interface, and sets the applicationContext to the SpringCloudRegistryFactory.applicationContext static property.The code is as follows:

// SpringCloudRegistryFactory.java

/**
 * The Dubbo services will be registered as the specified Spring cloud applications that will not be considered
 * normal ones, but only are used to Dubbo's service discovery even if it is based on Spring Cloud Commons abstraction.
 * However, current application will be registered by other DiscoveryClientAutoConfiguration.
 *
 */
public class DubboServiceRegistrationApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        // Set ApplicationContext into SpringCloudRegistryFactory before Dubbo Service Register
        SpringCloudRegistryFactory.setApplicationContext(applicationContext);
    }

}

7. metadata package

Before looking at the specific code, let's look at the configuration center interface in Nacos to see what Dubbo Metadata is.As shown in the following figure:

Perhaps this is not enough to clean up, let's take a more specific example of Dubbo Service Metadata, such as the following JSON string:

{
  "name" : "providers:dubbo:org.springframework.cloud.alibaba.dubbo.service.EchoService:1.0.0", // dubbo protocol
  "meta" : [ { // A Dubbo method
    "method" : { // Method Information
      "name" : "echo",
      "returnType" : "java.lang.String",
      "params" : [ {
        "index" : 0,
        "name" : "message",
        "type" : "java.lang.String"
      } ]
    },
    "request" : { // Request Information
      "method" : "GET",
      "url" : "/echo",
      "queries" : {
        "message" : [ "{message}" ]
      },
      "headers" : { }
    },
    "indexToName" : {
      "0" : [ "message" ]
    }
  }, { // A Dubbo method
    "method" : { // Method Information
      "name" : "plus",
      "returnType" : "java.lang.String",
      "params" : [ {
        "index" : 0,
        "name" : "a",
        "type" : "int"
      }, {
        "index" : 1,
        "name" : "b",
        "type" : "int"
      } ]
    },
    "request" : { // Request Information
      "method" : "POST",
      "url" : "/plus",
      "queries" : {
        "a" : [ "{a}" ],
        "b" : [ "{b}" ]
      },
      "headers" : { }
    },
    "indexToName" : {
      "0" : [ "a" ],
      "1" : [ "b" ]
    }
  } ]
}

*

7.1 Metadata class

There are six Metadata classes in the root directory of the metadata package.The following:

  • ServiceRestMetadata
  • RestMethodMetadata
  • MethodMetadata
  • MethodParameterMetadata
  • RequestMetadata
  • DubboTransportedMethodMetadata

7.1.1 ServiceRestMetadata

org.springframework.cloud.alibaba.dubbo.metadata.ServiceRestMetadata, Service Rest Metadata.The code is as follows:

// ServiceRestMetadata.java

public class ServiceRestMetadata {

    /**
     * service name
     */
    private String name;
    /**
     * Rest method metadata
     */
    private Set<RestMethodMetadata> meta;

    // ...omit the setting/getting method
}

7.1.2 RestMethodMetadata

org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata, Rest Method Metadata.The code is as follows:

// RestMethodMetadata.java

public class RestMethodMetadata {

    /**
     * method metadata
     */
    private MethodMetadata method;
    /**
     * request metadata
     */
    private RequestMetadata request;
    /**
     * TODO
     */
    private Map<Integer, Collection<String>> indexToName;

    // ...omit the setting/getting method
}

7.1.2.1 MethodMetadata

org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata, Method Metadata.The code is as follows:

// ServiceRestMetadata.java

public class MethodMetadata {

    /**
     * Method Name
     */
    private String name;
    /**
     * Return type
     */
    private String returnType;
    /**
     * Array of method parameter metadata
     */
    private List<MethodParameterMetadata> params;
    /**
     * Method
     */
    @JsonIgnore // Do not store in configuration center
    private Method method;

    public MethodMetadata() {
        this.params = new LinkedList<>();
    }

    public MethodMetadata(Method method) {
        this.name = method.getName();
        // Get return type
        this.returnType = ClassUtils.getName(method.getReturnType());
        // Initialize params
        this.params = initParameters(method);
        this.method = method;
    }

    private List<MethodParameterMetadata> initParameters(Method method) {
        // Get Number of Parameters
        int parameterCount = method.getParameterCount();
        // Returns an empty array if the parameter does not exist
        if (parameterCount < 1) {
            return Collections.emptyList();
        }
        // Create MethodParameterMetadata Array
        List<MethodParameterMetadata> params = new ArrayList<>(parameterCount);
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameterCount; i++) {
            // Get Parameter Object
            Parameter parameter = parameters[i];
            // Convert to MethodParameterMetadata object
            MethodParameterMetadata param = toMethodParameterMetadata(i, parameter);
            // Add to params
            params.add(param);
        }
        return params;
    }

    private MethodParameterMetadata toMethodParameterMetadata(int index, Parameter parameter) {
        // Create MethodParameterMetadata object
        MethodParameterMetadata metadata = new MethodParameterMetadata();
        metadata.setIndex(index); // Location of method parameters
        metadata.setName(parameter.getName()); // Name of method parameter
        metadata.setType(parameter.getType().getTypeName()); // Types of method parameters
        return metadata;
    }

    // ...omit the setting/getting method
}

7.1.2.1.1 MethodParameterMetadata

org.springframework.cloud.alibaba.dubbo.metadata.MethodParameterMetadata, Method Parameter Metadata.The code is as follows:

// MethodParameterMetadata.java

public class MethodParameterMetadata {

    /**
     * Location of method parameters
     */
    private int index;
    /**
     * Name of method parameter
     */
    private String name;
    /**
     * Types of method parameters
     */
    private String type;

    // ...omit the setting/getting method
}

7.1.2.2 RequestMetadata

org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata, Request Metadata.The code is as follows:

// RequestMetadata.java

public class RequestMetadata {

    /**
     * Method Name
     */
    private String method;
    /**
     * URL Route
     */
    private String url;

    private Map<String, Collection<String>> queries;

    private Map<String, Collection<String>> headers;

    public RequestMetadata() {
    }

    // Convert RequestTemplate object to RequestMetadata object
    public RequestMetadata(RequestTemplate requestTemplate) {
        this.method = requestTemplate.method();
        this.url = requestTemplate.url();
        this.queries = requestTemplate.queries();
        this.headers = requestTemplate.headers();
    }

    // ...omit the setting/getting method
}

7.1.2.3 DubboTransportedMethodMetadata

org.springframework.cloud.alibaba.dubbo.metadata.DubboTransportedMethodMetadata, inherits the MethodMetadata class, @DubboTransported comment corresponding MethodMetadata object.The code is as follows:

// DubboTransportedMethodMetadata.java

public class DubboTransportedMethodMetadata extends MethodMetadata {

    /**
     * Dubbo Agreement
     */
    private String protocol;
    /**
     * Dubbo Fault Tolerance Policy
     */
    private String cluster;

    // ...omit the setting/getting method
}

7.2 MetadataResolver

org.springframework.cloud.alibaba.dubbo.metadata.resolver.MetadataResolver, Metadata Resolver metadata parser interface.The code is as follows:

// MetadataResolver.java

/**
 * The REST metadata resolver
 */
public interface MetadataResolver {

    /**
     * Resolve the {@link ServiceRestMetadata} {@link Set set} from {@link ServiceBean}
     *
     * Resolve the ServiceRestMetadata collection for the specified ServiceBean
     *
     * @param serviceBean {@link ServiceBean}
     * @return non-null {@link Set}
     */
    Set<ServiceRestMetadata> resolveServiceRestMetadata(ServiceBean serviceBean);

    /**
     * Resolve {@link RestMethodMetadata} {@link Set set} from {@link Class target type}
     *
     * Resolve the ServiceRestMetadata collection for the specified class
     *
     * @param targetType {@link Class target type}
     * @return non-null {@link Set}
     */
    Set<RestMethodMetadata> resolveMethodRestMetadata(Class<?> targetType);

}

7.2.1 DubboServiceBeanMetadataResolver

DubboServiceBeanMetadataResolver Bean object, in 「5.2 DubboOpenFeignAutoConfiguration」 Is created.

org.springframework.cloud.alibaba.dubbo.metadata.resolver.DubboServiceBeanMetadataResolver, implements MetadataResolver, BeanClassLoaderAware, SmartInitializingSingleton interfaces, and implements MetadataResolver implementation classes based on Dubbo ServiceBean.

// DubboServiceBeanMetadataResolver.java

/**
 * The metadata resolver for {@link Feign} for {@link ServiceBean Dubbo Service Bean} in the provider side.
 */

7.2.1.1 Construction Method

// DubboServiceBeanMetadataResolver.java

private static final String[] CONTRACT_CLASS_NAMES = {
        "feign.jaxrs2.JAXRS2Contract",
        "org.springframework.cloud.openfeign.support.SpringMvcContract",
};

/**
 * Current application name
 *
 * However, this parameter is not currently used
 */
private final String currentApplicationName;
/**
 * Current Class Loader
 */
private ClassLoader classLoader;
private final ObjectProvider<Contract> contract;

/**
 * Feign Contract array
 *
 * https://www.jianshu.com/p/6582f8319f72
 */
private Collection<Contract> contracts;

public DubboServiceBeanMetadataResolver(String currentApplicationName, ObjectProvider<Contract> contract) {
    this.currentApplicationName = currentApplicationName;
    this.contract = contract;
}
  • For each property, let's look at the next one.

7.2.1.2 afterSingletonsInstantiated

Implement the #afterSingletonsInstantiated() method and initialize the contracts property.The code is as follows:

// DubboServiceBeanMetadataResolver.java

@Override
public void afterSingletonsInstantiated() {
    // <1>Create Feign Contract Array
    LinkedList<Contract> contracts = new LinkedList<>();

    // Add injected Contract if available, for example SpringMvcContract Bean under Spring Cloud Open Feign
    // <2.1>If contract exists, add to contracts
    contract.ifAvailable(contracts::add);

    // <2.2>Traverse the CONTRACT_CLASS_NAMES array, create the corresponding Contract object, and add it to the contracts
    Stream.of(CONTRACT_CLASS_NAMES)
            .filter(this::isClassPresent) // filter the existed classes
            .map(this::loadContractClass) // load Contract Class
            .map(this::createContract)    // create instance by the specified class
            .forEach(contracts::add);     // add the Contract instance into contracts

    // <3>Assign to contracts
    this.contracts = Collections.unmodifiableCollection(contracts);
}
  • At <1>, create the Feign Contract s array contracts.
  • At <2.1>, if contract exists, it is added to contracts.Normally, contract does not exist, so you can ignore it temporarily.
  • At <2.2>, iterate through the CONTRACT_CLASS_NAMES array, create the corresponding Contract object, and add it to contracts.Generally speaking, it exists.The code involved is as follows:

    // DubboServiceBeanMetadataResolver.java
    
    // Determine whether the specified className class exists
    private boolean isClassPresent(String className) {
        return ClassUtils.isPresent(className, classLoader);
    }
    
    // Load Contract Implementation Class corresponding to contractClassName
    private Class<?> loadContractClass(String contractClassName) {
        return ClassUtils.resolveClassName(contractClassName, classLoader);
    }
    
    // Create Contract Object
    private Contract createContract(Class<?> contractClassName) {
        return (Contract) BeanUtils.instantiateClass(contractClassName);
    }
    
  • <3>, assigned to this.contracts.

7.2.1.3 resolveMethodRestMetadata

Implement the #resolveMethodRestMetadata (Class<?> targetType) method to parse the ServiceRestMetadata collection for the specified class.The code is as follows:

// DubboServiceBeanMetadataResolver.java

@Override
public Set<RestMethodMetadata> resolveMethodRestMetadata(Class<?> targetType) {
    // <1>Get Method Collection
    List<Method> feignContractMethods = selectFeignContractMethods(targetType);
    // <2>Convert to RestMethodMetadata Collection
    return contracts.stream() // Traversing contracts array
            .map(contract -> contract.parseAndValidatateMetadata(targetType)) // <2.1>Returns an array of Feign MethodMetadata of the target type
            .flatMap(Collection::stream)
            .map(methodMetadata -> resolveMethodRestMetadata(methodMetadata, targetType, feignContractMethods)) // <2.2>Convert Feign MethodMetadata to RestMethodMetadata objects
            .collect(Collectors.toSet()); // Convert to Set
}
  • At <1>, call the #selectFeignContractMethods (Class<?> targetType) method to get a Method collection.The code is as follows:

    // DubboServiceBeanMetadataResolver.java
    
    /**
     * Select feign contract methods
     * <p>
     * extract some code from {@link Contract.BaseContract#parseAndValidatateMetadata(java.lang.Class)}
     *
     * @param targetType
     * @return non-null
     */
    private List<Method> selectFeignContractMethods(Class<?> targetType) {
        List<Method> methods = new LinkedList<>();
        // Ways to traverse the target
        for (Method method : targetType.getMethods()) {
            // ignore
            if (method.getDeclaringClass() == Object.class || // Methods declared by Object s, such as the equals method
                    (method.getModifiers() & Modifier.STATIC) != 0 || // Static method
                    Util.isDefault(method)) { // Feign default method
                continue;
            }
            methods.add(method);
        }
        return methods;
    }
    
  • At <2>, convert to RestMethodMetadata collection.

  • At <2.1>, call the Contract#parseAndValidate Metadata() method to return an array of Feign MethodMetadata of the target type.This code belongs to Feign, so let's take a look at an example of the result.As follows:

    • One thing to note here is that since there are JAXRS2Contract classes and SpringMvcContract classes in CONTRACT_CLASS_NAMES, both JSR311 and Spring MVC annotations are required.For instance:

      // DefaultEchoService.java
      
      @Service(version = "1.0.0", protocol = {"dubbo", "rest"})
      @RestController // Spring MVC Notes
      @Path("/") // JSR311 Comment
      public class DefaultEchoService implements EchoService {
      
          @Override
          @GetMapping(value = "/echo"
      //            consumes = MediaType.APPLICATION_JSON_VALUE,
      //            produces = MediaType.APPLICATION_JSON_UTF8_VALUE
          ) // Spring MVC Notes
          @Path("/echo") // JSR311 Comment
          @GET // JSR311 Comment
      //    @Consumes("application/json")
      //    @Produces("application/json;charset=UTF-8")
          public String echo(@RequestParam // Spring MVC Notes
                             @QueryParam("message") String message) { // JSR311 Comment
              System.out.println(message);
              return RpcContext.getContext().getUrl() + " [echo] : " + message;
          }
      }
      
      • However, Gentiana does not understand why it is so designed for the time being.Fat friends with knowledge, trouble to educate, hee-hee~
  • At <2.2>, invoke the #resolveMethodRestMetadata (MethodMetadata MethodMetadata, Class<?> targetType, List<Method> feignContractMethods) method to convert Feign MethodMetadata to a RestMethodMetadata object.The code is as follows:

    // DubboServiceBeanMetadataResolver.java
    
    protected RestMethodMetadata resolveMethodRestMetadata(MethodMetadata methodMetadata, // Feign MethodMetadata
                                                           Class<?> targetType,
                                                           List<Method> feignContractMethods) {
        // Get configKey.For example: DefaultEchoService#echo(String)
        String configKey = methodMetadata.configKey();
        // Get Matched Method
        Method feignContractMethod = getMatchedFeignContractMethod(targetType, feignContractMethods, configKey);
        // Create a RestMethodMetadata object and set its properties
        RestMethodMetadata metadata = new RestMethodMetadata();
        metadata.setRequest(new RequestMetadata(methodMetadata.template()));
        metadata.setMethod(new org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata(feignContractMethod));
        metadata.setIndexToName(methodMetadata.indexToName());
        return metadata;
    }
    
    private Method getMatchedFeignContractMethod(Class<?> targetType, List<Method> methods, String expectedConfigKey) {
        Method matchedMethod = null;
        // Traversing a Method Collection
        for (Method method : methods) {
            // CongKey to get this method
            String configKey = Feign.configKey(targetType, method);
            // If equal, return.
            if (expectedConfigKey.equals(configKey)) {
                matchedMethod = method;
                break;
            }
        }
        return matchedMethod;
    }
    
    • The code for object conversion is easy to understand.

7.2.1.4 resolveServiceRestMetadata

Implement the #resolveServiceRestMetadata(ServiceBean serviceBean) method to parse the ServiceRestMetadata collection for the specified ServiceBean.The code is as follows:

// DubboServiceBeanMetadataResolver.java

@Override
public Set<ServiceRestMetadata> resolveServiceRestMetadata(ServiceBean serviceBean) {
    // <1.1>Get the Bean object applied
    Object bean = serviceBean.getRef();
    // <1.2>Get Bean Type
    Class<?> beanType = bean.getClass();
    // <1.3>Resolve the RestMethodMetadata collection corresponding to the Bean type
    Set<RestMethodMetadata> methodRestMetadata = resolveMethodRestMetadata(beanType);

    // <2.1>Create ServiceRestMetadata Array
    Set<ServiceRestMetadata> serviceRestMetadata = new LinkedHashSet<>();
    // <2.2>Get the set of URL s exposed by ServiceBean
    List<URL> urls = serviceBean.getExportedUrls();
    // <2.3>Traverse through the URL collection, encapsulate the RestMethodMetadata collection, encapsulate it as a ServiceRestMetadata object, add it to the serviceRestMetadata, and return.
    urls.stream()
            .map(SpringCloudRegistry::getServiceName)
            .forEach(serviceName -> {
                ServiceRestMetadata metadata = new ServiceRestMetadata();
                metadata.setName(serviceName);
                metadata.setMeta(methodRestMetadata);
                serviceRestMetadata.add(metadata);
            });
    return serviceRestMetadata;
}
  • At <1.3>, call the #resolveMethodRestMetadata (Class<?> targetType) method to resolve the RestMethodMetadata collection corresponding to the Bean type.That is, we are 「7.2.1.3 resolveMethodRestMetadata」 Method of parsing.
  • At <2.1>, get the set of URLs exposed by ServiceBean.For example, the DefaultEchoService example in this article exposes the URLs of the dubbo:// and rest:// protocols.
  • At <2.3>, traverse the URL collection, encapsulate the RestMethodMetadata collection as a ServiceRestMetadata object, add it to the serviceRestMetadata, and return.The SpringCloudRegistry::getServiceName snippet here is to call the SpringCloudRegistry#getServiceName(URL url) method to get the Dubbo service name.For example: providers:dubbo:org.springframework.cloud.alibaba.dubbo.service.EchoService:1.0.0.

So far, the Dubbo Service metadata has been parsed.We'll see two things later:

  • 1. Store metadata in the configuration center.This allows service consumers to share data to that section.
  • 2. Service consumers can make generalization calls using Dubbo based on Dubbo Service metadata.

Of course, this part of the Dubbo Service metadata is not needed if you do not use Dubbo to make generalization calls.Why?Fat, friendly and thoughtful

7.3 DubboTransportedMethodMetadataResolver

org.springframework.cloud.alibaba.dubbo.metadata.resolver.DubboTransportedMethodMetadataResolver, parses MethodMetadata data data for the @DubboTransported annotation.The code is as follows:

// DubboTransportedMethodMetadataResolver.java

public Map<DubboTransportedMethodMetadata, RequestMetadata> resolve(Class<?> targetType) {
    // <1>Get the DubboTransportedMethodMetadata collection for the specified class
    Set<DubboTransportedMethodMetadata> dubboTransportedMethodMetadataSet = resolveDubboTransportedMethodMetadataSet(targetType);
    // <2>Gets the RequestMetadata mapping for the specified class.Where KEY is configKey
    Map<String, RequestMetadata> requestMetadataMap = resolveRequestMetadataMap(targetType);
    // <3>Convert to a mapping of DubboTransportedMethodMetadata and RequestMetadata
    return dubboTransportedMethodMetadataSet
            .stream()
            .collect(Collectors.toMap(methodMetadata -> methodMetadata, methodMetadata ->
                    requestMetadataMap.get(Feign.configKey(targetType, methodMetadata.getMethod()))
            ));
}
  • At <1>, call the #resolveDubboTransportedMethodMetadataSet (Class<?> targetType) method to get the DubboTransportedMethodMetadata collection of the specified class.The code is as follows:

    // DubboTransportedMethodMetadataResolver.java
    
    protected Set<DubboTransportedMethodMetadata> resolveDubboTransportedMethodMetadataSet(Class<?> targetType) {
        // The public methods of target interface
        Method[] methods = targetType.getMethods();
        // Create an array of DubboTransportedMethodMetadata
        Set<DubboTransportedMethodMetadata> methodMetadataSet = new LinkedHashSet<>();
        // traversal method
        for (Method method : methods) {
            // If there is a @DubboTransported comment
            DubboTransported dubboTransported = resolveDubboTransported(method); // ①
            // If so, create a DubboTransportedMethodMetadata object and add it to the methodMetadataSet
            if (dubboTransported != null) {
                // Create 2
                DubboTransportedMethodMetadata methodMetadata = createDubboTransportedMethodMetadata(method, dubboTransported);
                // Add to
                methodMetadataSet.add(methodMetadata);
            }
        }
        return methodMetadataSet;
    }
    
    // ①
    private DubboTransported resolveDubboTransported(Method method) {
        // First from the method, get the @DubboTransported annotation
        DubboTransported dubboTransported = AnnotationUtils.findAnnotation(method, DUBBO_TRANSPORTED_CLASS);
        // If not, get the @DubboTransported annotation from the class
        if (dubboTransported == null) { // Attempt to find @DubboTransported in the declaring class
            Class<?> declaringClass = method.getDeclaringClass();
            dubboTransported = AnnotationUtils.findAnnotation(declaringClass, DUBBO_TRANSPORTED_CLASS);
        }
        return dubboTransported;
    }
    
    // ②
    private DubboTransportedMethodMetadata createDubboTransportedMethodMetadata(Method method, DubboTransported dubboTransported) {
        // Create DubboTransportedMethodMetadata object
        DubboTransportedMethodMetadata methodMetadata = new DubboTransportedMethodMetadata(method);
       // Resolve properties and set them to methodMetadata
        String protocol = propertyResolver.resolvePlaceholders(dubboTransported.protocol());
        String cluster = propertyResolver.resolvePlaceholders(dubboTransported.cluster());
        methodMetadata.setProtocol(protocol);
        methodMetadata.setCluster(cluster);
        return methodMetadata;
    }
    
  • At <2>, call the #resolveRequestMetadataMap (Class<?> targetType) method to get the RequestMetadata mapping for the specified class.Where KEY is configKey.The code is as follows:

    // DubboTransportedMethodMetadataResolver.java
    
    private Map<String, RequestMetadata> resolveRequestMetadataMap(Class<?> targetType) {
        return contract.parseAndValidatateMetadata(targetType) // Gets the Feign MethodMetadata collection of the specified class
                .stream().collect(Collectors.toMap(feign.MethodMetadata::configKey, this::requestMetadata)); // Create RequestMetadata object
    }
    
    private RequestMetadata requestMetadata(feign.MethodMetadata methodMetadata) {
        return new RequestMetadata(methodMetadata.template());
    }
    
  • At <3>, convert to a mapping of DubboTransportedMethodMetadata and RequestMetadata.

Subsequently, this class will be 「8.1 TargeterInvocationHandler」 Called.

7.4 MetadataConfigService

Use for service providers.

org.springframework.cloud.alibaba.dubbo.metadata.service.MetadataConfigService, the metadata configuration service interface, allows you to read and write metadata from the configuration center.The code is as follows:

// MetadataConfigService.java

public interface MetadataConfigService {

    /**
     * Publish Rest metadata for the specified service
     *
     * @param serviceName service name
     * @param serviceRestMetadata ServiceRestMetadata aggregate
     */
    void publishServiceRestMetadata(String serviceName, Set<ServiceRestMetadata> serviceRestMetadata);

    /**
     * Get Rest metadata for the specified service
     *
     * @param serviceName service name
     * @return ServiceRestMetadata aggregate
     */
    Set<ServiceRestMetadata> getServiceRestMetadata(String serviceName);

}

7.4.1 NacosMetadataConfigService

org.springframework.cloud.alibaba.dubbo.metadata.service.NacosMetadataConfigService, which implements the MetadataConfigService interface, is an implementation class based on Nacos as the configuration center.

7.4.1.1 Construction Method

// NacosMetadataConfigService.java

/**
 * ObjectMapper ,Using Jackson serialization and deserialization
 */
private final ObjectMapper objectMapper = new ObjectMapper();

/**
 * NacosConfigProperties Object to get {@link #configService}
 */
@Autowired
private NacosConfigProperties nacosConfigProperties;

private ConfigService configService;

@PostConstruct
public void init() {
    // Initialize configService properties
    this.configService = nacosConfigProperties.configServiceInstance();
    // Turn on JSON formatting
    this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
}

7.4.1.2 publishServiceRestMetadata

Implement the #publishServiceRestMetadata (String serviceName, Set<ServiceRestMetadata> serviceRestMetadata) method with the following code:

// NacosMetadataConfigService.java

@Override
public void publishServiceRestMetadata(String serviceName, Set<ServiceRestMetadata> serviceRestMetadata) {
    // Get Nacos dataId1
    String dataId = getServiceRestMetadataDataId(serviceName);
    // Serialize the ServiceRestMetadata collection into a json string 2
    String json = writeValueAsString(serviceRestMetadata);
    // Write to Nacos Configuration Center
    try {
        configService.publishConfig(dataId, DEFAULT_GROUP, json);
    } catch (NacosException e) {
        throw new RuntimeException(e);
    }
}

/**
 * Get the data Id of service rest metadata
 */
private static String getServiceRestMetadataDataId(String serviceName) { // ①
    return "metadata:rest:" + serviceName + ".json";
}

private String writeValueAsString(Object object) { // ②
    String content;
    try {
        content = objectMapper.writeValueAsString(object);
    } catch (JsonProcessingException e) {
        throw new IllegalArgumentException(e);
    }
    return content;
}

7.4.1.3 getServiceRestMetadata

Implement the #getServiceRestMetadata(String serviceName) method with the following code:

// NacosMetadataConfigService.java

@Override
public Set<ServiceRestMetadata> getServiceRestMetadata(String serviceName) {
    Set<ServiceRestMetadata> metadata;
    // Get Nacos dataId
    String dataId = getServiceRestMetadataDataId(serviceName);
    try {
        // Read the json string from the Nacos Configuration Center
        String json = configService.getConfig(dataId, DEFAULT_GROUP, 1000 * 3);
        // Deserialize the json string into a ServiceRestMetadata collection
        metadata = objectMapper.readValue(json, TypeFactory.defaultInstance().constructCollectionType(LinkedHashSet.class, ServiceRestMetadata.class));
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    return metadata;
}

7.5 DubboServiceMetadataRepository

For service consumers.

Org.springframework.cloud.alibaba.dubbo.metadata.repository.Dubbo Service Metadata Repository,Dubbo Service Metadata Warehouse.With this class, service consumers can get a ReferenceBean object for each corresponding Dubbo service~

7.5.1 Construction Method

// DubboServiceMetadataRepository.java

/**
 * RequestMetadata Mapping with ReferenceBean
 *
 * Key is application name
 * Value is  Map<RequestMetadata, ReferenceBean<GenericService>>
 */
private Map<String, Map<RequestMetadata, ReferenceBean<GenericService>>> referenceBeansRepository = new HashMap<>();

/**
 * RequestMetadata Mapping with MethodMetadata
 *
 * Key is application name
 */
private Map<String, Map<RequestMetadata, MethodMetadata>> methodMetadataRepository = new HashMap<>();

@Autowired
private MetadataConfigService metadataConfigService;
  • Look at the comments on each property.
  • It is important to note that the Key is application name, which means that the first KEY is the Spring Cloud (Spring Boot) application name.
    • However, there can be multiple Dubbo services in a Dubbo application, so there is a second Map, such as Map <RequestMetadata, ReferenceBean <GenericService>.
    • Of course, here is the request information corresponding to each Dubbo Service method in terms of RequestMetadata.This is OK because each request path has a unique URL.It's equivalent to tiling each Dubbo Service's approach.

7.5.2 updateMetadata

The #updateMetadata(String serviceName) method initializes the metadata for the specified serviceName.The code is as follows:

// DubboServiceMetadataRepository.java

public void updateMetadata(String serviceName) {
    // <1.1>Get the mapping of RequestMetadata and ReferenceBean corresponding to serviceName
    Map<RequestMetadata, ReferenceBean<GenericService>> genericServicesMap = referenceBeansRepository.computeIfAbsent(serviceName, k -> new HashMap<>());
    // <1.2>Get the mapping of RequestMetadata and MethodMetadata corresponding to serviceName
    Map<RequestMetadata, MethodMetadata> methodMetadataMap = methodMetadataRepository.computeIfAbsent(serviceName, k -> new HashMap<>());
    // <1.3>Get the ServiceRestMetadata collection corresponding to the serviceName
    Set<ServiceRestMetadata> serviceRestMetadataSet = metadataConfigService.getServiceRestMetadata(serviceName);

    // <2>Walk through the ServiceRestMetadata collection, create the corresponding ReferenceBean, and get the corresponding MethodMetadata object
    for (ServiceRestMetadata serviceRestMetadata : serviceRestMetadataSet) {
        // <2.1>Create a corresponding ReferenceBean
        ReferenceBean<GenericService> referenceBean = adaptReferenceBean(serviceRestMetadata);
        // <2.2>Traverse the RestMethodMetadata collection, add to generic ServicesMap and methodMetadataMap, and cache
        serviceRestMetadata.getMeta().forEach(restMethodMetadata -> {
            RequestMetadata requestMetadata = restMethodMetadata.getRequest();
            genericServicesMap.put(requestMetadata, referenceBean);
            methodMetadataMap.put(requestMetadata, restMethodMetadata.getMethod());
        });
    }
}
  • At <1.3>, call the MetadataConfigService#getServiceRestMetadata(String serviceName) method to get the ServiceRestMetadata collection for the specified serviceName.Here, we're using it. 「7.4 MetadataConfigService」.
  • At <2>, iterate through the ServiceRestMetadata collection, create the corresponding ReferenceBean, and get the corresponding MethodMetadata object.

    • At <2.1>, call the #adaptReferenceBean(ServiceRestMetadata serviceRestMetadata) method to create a ReferenceBean object.The code is as follows:

      // DubboServiceMetadataRepository.java
      
      private ReferenceBean<GenericService> adaptReferenceBean(ServiceRestMetadata serviceRestMetadata) {
          // Get corresponding properties
          String dubboServiceName = serviceRestMetadata.getName();
          String[] segments = SpringCloudRegistry.getServiceSegments(dubboServiceName);
          String interfaceName = SpringCloudRegistry.getServiceInterface(segments);
          String version = SpringCloudRegistry.getServiceVersion(segments);
          String group = SpringCloudRegistry.getServiceGroup(segments);
      
          // Create a ReferenceBean object and set related properties
          ReferenceBean<GenericService> referenceBean = new ReferenceBean<GenericService>();
          referenceBean.setGeneric(true);
          referenceBean.setInterface(interfaceName);
          referenceBean.setVersion(version);
          referenceBean.setGroup(group);
          return referenceBean;
      }
      
      • Notice that the ReferenceBean created at this point is a generalized reference to Dubbo.
    • At <2.2>, iterate through the RestMethodMetadata collection, add to generic ServicesMap and methodMetadataMap, and cache.

7.5.3 getReferenceBean

#getReferenceBean(String serviceName, RequestMetadata requestMetadata) method to get the ReferenceBean object corresponding to the specified RequestMetadata for the specified application.The code is as follows:

public ReferenceBean<GenericService> getReferenceBean(String serviceName, RequestMetadata requestMetadata) {
    return getReferenceBeansMap(serviceName).get(requestMetadata);
}

private Map<RequestMetadata, ReferenceBean<GenericService>> getReferenceBeansMap(String serviceName) {
    return referenceBeansRepository.getOrDefault(serviceName, Collections.emptyMap());
}

7.5.4 getMethodMetadata

The #getMethodMetadata(String serviceName, RequestMetadata requestMetadata) method obtains the MethodMetadata object corresponding to the specified RequestMetadata for the specified application.The code is as follows:

// DubboServiceMetadataRepository.java

public MethodMetadata getMethodMetadata(String serviceName, RequestMetadata requestMetadata) {
    return getMethodMetadataMap(serviceName).get(requestMetadata);
}

private Map<RequestMetadata, MethodMetadata> getMethodMetadataMap(String serviceName) {
    return methodMetadataRepository.getOrDefault(serviceName, Collections.emptyMap());
}

7.6 Summary

So far, we've seen all the code under the metadata package.Because this subsection is more about metadata analysis, storage, reading and does not involve specific use, it will be slightly confusing.However, after the next section, openfeign, Feign makes a generic call to the corresponding Dubbo service to use the metadata.At this point, we can get the whole process through.

8. openfeign package

In this section, let's see how Dubbo integrates with Feign.

8.1 TargeterBeanPostProcessor

org.springframework.cloud.alibaba.dubbo.openfeign.TargeterBeanPostProcessor, implements BeanPostProcessor, BeanClassLoaderAware interfaces, handles Beans of openfeign Targeter type, and creates their dynamic proxies, enabling Dubbo to be integrated into Openfeign.The code is as follows:

// TargeterBeanPostProcessor.java

public class TargeterBeanPostProcessor implements BeanPostProcessor, BeanClassLoaderAware {

    private static final String TARGETER_CLASS_NAME = "org.springframework.cloud.openfeign.Targeter";

    private final Environment environment;

    private final DubboServiceMetadataRepository dubboServiceMetadataRepository;

    private ClassLoader classLoader;

    public TargeterBeanPostProcessor(Environment environment, DubboServiceMetadataRepository dubboServiceMetadataRepository) {
        this.environment = environment;
        this.dubboServiceMetadataRepository = dubboServiceMetadataRepository;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
        // <1>Classes for Getting Bean s
        Class<?> beanClass = ClassUtils.getUserClass(bean.getClass());
        // <2>Get the openfeign Targeter interface
        Class<?> targetClass = ClassUtils.resolveClassName(TARGETER_CLASS_NAME, classLoader);
        // <3>Create a dynamic proxy if the openfeign Targeter interface is implemented
        if (targetClass.isAssignableFrom(beanClass)) {
            return Proxy.newProxyInstance(classLoader, new Class[]{targetClass},
                    new TargeterInvocationHandler(bean, environment, dubboServiceMetadataRepository));
        }
        return bean;
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

}
  • Before analyzing the specific code, fat friends look at it first. "Design Implementation of Spring Cloud Alibaba Sentinel Integrated Feign" Of "Execution of Feign" Subsection.Because you don't know Feign's internal working mechanism very well when you look at it, you can also refer to this subsection to read this part of the code.
  • At <1>, get the class of the Bean.From the above recommended article, we know that the HystrixTargeter or DefaultTargeter class is returned at this time.
  • At <2>, get the openfeign Targeter interface.
  • At <3>, if the openfeign Targeter interface is implemented, a dynamic proxy is created.The processor passed in is the TargeterInvocationHandler object.For a detailed analysis, see 「8.2 TargeterInvocationHandler」 .

8.2 TargeterInvocationHandler

org.springframework.cloud.alibaba.dubbo.openfeign.TargeterInvocationHandler, which intercepts Targeter's #target (FeignClientFactoryBean Factory, Builder feign, FeignContext, HardCodedTarget<T>) methods, creates different proxy objects based on the conditions.The code is as follows:

If you don't understand Targeter, look again. "Design Implementation of Spring Cloud Alibaba Sentinel Integrated Feign" Of "Execution of Feign" Subsection.

// TargeterInvocationHandler.java

class TargeterInvocationHandler implements InvocationHandler {

    private final Object bean;

    private final Environment environment;

    private final DubboServiceMetadataRepository dubboServiceMetadataRepository;

    TargeterInvocationHandler(Object bean, Environment environment,
                              DubboServiceMetadataRepository dubboServiceMetadataRepository) {
        this.bean = bean;
        this.environment = environment;
        this.dubboServiceMetadataRepository = dubboServiceMetadataRepository;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        /**
         * args[0]: FeignClientFactoryBean factory
         * args[1]: Feign.Builder feign
         * args[2]: FeignContext context
         * args[3]: Target.HardCodedTarget<T> target
         */
        FeignContext feignContext = cast(args[2]);
        Target.HardCodedTarget<?> target = cast(args[3]);

        // <1>Call the original target method first to return the default proxy object
        // Execute Targeter#target method first
        method.setAccessible(true);
        // Get the default proxy object
        Object defaultProxy = method.invoke(bean, args);
        // Create Dubbo Proxy if required
        // If the creation of a Dubbo proxy object is compliant, a Dubbo proxy object is created.
        // Otherwise, use the defaultProxy proxy by default
        return createDubboProxyIfRequired(feignContext, target, defaultProxy);
    }

    // ...omit the method to be explained below
}
  • To make fat friends understand better, let's take a look at the screenshot of the method when it was called:
  • At <1>, call the original target method first, and return the default proxy object.
  • At <2>, call the #createDubboProxyIfRequired(FeignContext feignContext, Target target, Object defaultProxy) method, and create a Dubbo proxy object if it complies with creating a Dubbo proxy object.Otherwise, use the defaultProxy proxy by default.Then the question comes. What are the conditions?There is a @DubboTransported annotation and the service provider's metadata cannot be pulled from the configuration center.Because we cannot use Dubbo's generalized calls without the service provider's metadata.
  • so, let's keep looking down.

8.2.1 createDubboProxyIfRequired

#createDubboProxyIfRequiredcreateDubboProxyIfRequired(FeignContext feignContext, Target target, Object defaultProxy) method, creates the corresponding proxy object according to the condition.The code is as follows:

// TargeterInvocationHandler.java

private Object createDubboProxyIfRequired(FeignContext feignContext, Target target, Object defaultProxy) {
    // <1>Try to create a DubboInvocationHandler
    DubboInvocationHandler dubboInvocationHandler = createDubboInvocationHandler(feignContext, target, defaultProxy);
    // <2.1>Returns the default Proxy proxy if it is not created successfully, indicating it does not meet the criteria
    if (dubboInvocationHandler == null) {
        return defaultProxy;
    }
    // <2.2>Create a dynamic proxy using the dubboInvocationHandler if the creation succeeds and the condition is met
    return Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, dubboInvocationHandler);
}
  • At <1>, call the #createDubboInvocationHandler(FeignContext feignContext, Target target, Object defaultFeignClientProxy) method to attempt to create a DubboInvocationHandler.
  • At <2.1>, if a successful creation indicates that the condition is not met, the default Proxy proxy is returned.
  • At <2.2>, a dynamic proxy using dubboInvocationHandler is created if the creation is successful and qualifies.

8.2.2 createDubboInvocationHandler

#createDubboInvocationHandler(FeignContext feignContext, Target target, Object defaultFeignClientProxy) method to create a DubboInvocationHandler object.The code is as follows:

In the future, this piece of logic will be extracted into the org.springframework.cloud.alibaba.dubbo.openfeign.DubboInvocationHandlerFactory class.

// TargeterInvocationHandler.java

private DubboInvocationHandler createDubboInvocationHandler(FeignContext feignContext, Target target, Object defaultFeignClientProxy) {
    // Service name equals @FeignClient.name()
    String serviceName = target.name();
    Class<?> targetType = target.type();

    // Get Contract Bean from FeignContext
    // <1.1>Get Feign Contract
    Contract contract = feignContext.getInstance(serviceName, Contract.class);
    // <1.2>Create the DubboTransportedMethodMetadataResolver object
    DubboTransportedMethodMetadataResolver resolver = new DubboTransportedMethodMetadataResolver(environment, contract);
    // <1.3>Resolve the specified class to get its mapping of DubboTransportedMethodMetadata and RequestMetadata
    Map<DubboTransportedMethodMetadata, RequestMetadata> methodRequestMetadataMap = resolver.resolve(targetType);
    // <1.4>If empty, return indicating that the condition is not met
    if (methodRequestMetadataMap.isEmpty()) { // @DubboTransported method was not found
        return null;
    }

    // Update Metadata
    // <2>Initialize the metadata for the specified `serviceName'.Here, metadata is obtained from the Configuration Center
    dubboServiceMetadataRepository.updateMetadata(serviceName);

    Map<Method, org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata> methodMetadataMap = new HashMap<>();
    Map<Method, GenericService> genericServicesMap = new HashMap<>();
    // <3>Traverse the methodRequestMetadataMap collection and initialize its GenericService
    methodRequestMetadataMap.forEach((dubboTransportedMethodMetadata, requestMetadata) -> {
        // <3.1.1>Get the ReferenceBean object and initialize its properties
        ReferenceBean<GenericService> referenceBean = dubboServiceMetadataRepository.getReferenceBean(serviceName, requestMetadata);
        referenceBean.setProtocol(dubboTransportedMethodMetadata.getProtocol());
        referenceBean.setCluster(dubboTransportedMethodMetadata.getCluster());
        // <3.1.2>Add to genericServicesMap
        genericServicesMap.put(dubboTransportedMethodMetadata.getMethod(), referenceBean.get());
        // <3.2.1>Get MethodMetadata Object
        org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata methodMetadata = dubboServiceMetadataRepository.getMethodMetadata(serviceName, requestMetadata);
        // <3.2.2>Add to methodMetadataMap
        methodMetadataMap.put(dubboTransportedMethodMetadata.getMethod(), methodMetadata);
    });

    // <4.1>Get default FeignClientProxy, default InvocationHandler object
    InvocationHandler defaultFeignClientInvocationHandler = Proxy.getInvocationHandler(defaultFeignClientProxy);
    // <4.2>Create a DubboInvocationHandler object
    return new DubboInvocationHandler(genericServicesMap, methodMetadataMap, defaultFeignClientInvocationHandler);
}
  • Get Feign Contract at <1.1>.
  • At <1.2>, create the DubboTransportedMethodMetadataResolver object.
  • At <1.3>, call DubboTransportedMethodMetadataResolver#resolve (Class<?> targetType) to resolve the specified class and get its mapping of DubboTransportedMethodMetadata and RequestMetadata.Here, we'll take the 「7.3 DubboTransportedMethodMetadata」 String it up.
  • <1.4>, if empty, returns indicating that the condition is not met.Let's throw a question here. If some of the methods in a class have no @DubboTransported annotation, would a DubboInvocationHandler be created?The answer is yes.At this point, there will be a method for @DubboTransported annotation, using Dubbo for invocation, a method without @DubboTransported annotation, or choosing a method provided by the original Feign (for example, RestTemplate) for invocation.
  • At <2>, call the DubboServiceMetadataRepository#updateMetadata(String serviceName) method to initialize the metadata for the specified serviceName (at this point, metadata is obtained from the configuration center).Here, we'll take the 「7.5 DubboServiceMetadataRepository」 String it up.
  • At <3>, iterate through the methodRequestMetadataMap collection to initialize its GenericService.
    • At <3.1.1>, get the ReferenceBean object and initialize its properties.
    • <3.1.2>, added to generic ServicesMap.
    • Get the MethodMetadata object at <3.2.1>.
    • <3.2.2>, added to methodMetadataMap.
  • At <4.1>, get the default InvocationHandler object in defaultFeignClientProxy.Why do I need it?Because, in a class, there may be a method with the @DubboTransported annotation.
  • At <4.2>, create the DubboInvocationHandler object.

8.3 DubboInvocationHandler

org.springframework.cloud.alibaba.dubbo.openfeign.DubboInvocationHandler, implements the InvocationHandler interface, and implements the Dubbo InvocationHandler class.The code is as follows:

// DubboInvocationHandler.java

public class DubboInvocationHandler implements InvocationHandler {

    private final Map<Method, GenericService> genericServicesMap;

    private final Map<Method, MethodMetadata> methodMetadata;

    private final InvocationHandler defaultInvocationHandler;

    public DubboInvocationHandler(Map<Method, GenericService> genericServicesMap,
                                  Map<Method, MethodMetadata> methodMetadata,
                                  InvocationHandler defaultInvocationHandler) {
        this.genericServicesMap = genericServicesMap;
        this.methodMetadata = methodMetadata;
        this.defaultInvocationHandler = defaultInvocationHandler;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // Get GenericService Object
        GenericService genericService = genericServicesMap.get(method);
        // Get the MethodMetadata object
        MethodMetadata methodMetadata = this.methodMetadata.get(method);

        // <1>Scenario 1, if either does not exist, use the defaultInvocationHandler
        if (genericService == null || methodMetadata == null) {
            return defaultInvocationHandler.invoke(proxy, method, args);
        }

        // Scenario 2, make a generalization call
        String methodName = methodMetadata.getName(); // Method Name
        String[] parameterTypes = methodMetadata
                .getParams()
                .stream()
                .map(MethodParameterMetadata::getType)
                .toArray(String[]::new); // Parameter type
        return genericService.$invoke(methodName, parameterTypes, args);
    }

}
  • At <1>, if either does not exist, use the defaultInvocationHandler, the method without the @DubboTransported annotation.
  • At <2>, perform a generalization call, a method annotated with @DubboTransported.

8.4 Summary

At this point, the entire process of service consumer invocation has been concatenated.Because this article is written in package hierarchy, coherence is relatively poor.Therefore, fat friends need to debug themselves.

9. Regisry Package

In this section, let's see how Dubbo integrates with the Spring Cloud Registry.

In 2.4.2 application.yaml In, we can see that the registry uses dubbo.registry.address: spring-cloud://nacos.

9.1 Registration

org.springframework.cloud.alibaba.dubbo.registry.DubboRegistration, implements the Spring Cloud Registration interface, and implements the Dubbo Registration class.The code is as follows:

// DubboRegistration.java

/**
 * The {@link Registration} of Dubbo uses an external of {@link ServiceInstance} instance as the delegate.
 */
class DubboRegistration implements Registration {

    /**
     * Spring Cloud ServiceInstance
     */
    private final ServiceInstance delegate;

    public DubboRegistration(ServiceInstance delegate) {
        this.delegate = delegate;
    }

    @Override
    public String getServiceId() {
        return delegate.getServiceId();
    }

    @Override
    public String getHost() {
        return delegate.getHost();
    }

    @Override
    public int getPort() {
        return delegate.getPort();
    }

    @Override
    public boolean isSecure() {
        return delegate.isSecure();
    }

    @Override
    public URI getUri() {
        return delegate.getUri();
    }

    @Override
    public Map<String, String> getMetadata() {
        return delegate.getMetadata();
    }

    @Override
    public String getScheme() {
        return delegate.getScheme();
    }

}

9.2 SpringCloudRegistryFactory

In the com.alibaba.dubbo.registry.RegistryFactory, a SpringCloudRegistryFactory extension is declared.The following:

spring-cloud=org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegistryFactory
  • The prefix is "spring-cloud".That is, it corresponds to our configured dubbo.registry.address: spring-cloud://nacos.

org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegistryFactory, implements the Dubbo RegistryFactory interface, and creates a registry for SpringCloudRegistry.The code is as follows:

// SpringCloudRegistryFactory.java

public class SpringCloudRegistryFactory implements RegistryFactory {

    private static ApplicationContext applicationContext;

    @Override
    public Registry getRegistry(URL url) {
        // <1>Get the ServiceRegistry object
        ServiceRegistry<Registration> serviceRegistry = applicationContext.getBean(ServiceRegistry.class);
        // <2>Get the DiscoveryClient object
        DiscoveryClient discoveryClient = applicationContext.getBean(DiscoveryClient.class);
        // <3>Create SpringCloudRegistry object
        return new SpringCloudRegistry(url, serviceRegistry, discoveryClient);
    }

    public static void setApplicationContext(ApplicationContext applicationContext) {
        SpringCloudRegistryFactory.applicationContext = applicationContext;
    }

}
  • <1>, get the ServiceRegistry object.Here, if we use Nacos as the registry, the returned object is the org.springframework.cloud.alibaba.nacos.registry.NacosServiceRegistry.
  • <2>, get the DiscoveryClient object.Here, if we use Nacos as the registry, the Composite DiscoveryClient object returned contains the org.springframework.cloud.alibaba.nacos.NacosDiscoveryClient object.
  • These two variables are shown in the following figure:
  • At <3>, create the SpringCloudRegistry object.For a detailed analysis, see 「9.3 SpringCloudRegistry」 .

9.3 SpringCloudRegistry

This section is based on what fat friends have seen. Perfect Dubbo Source Analysis - Abstract API of Registry (1) Articles.

org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegistry, inherits the Dubbo FailbackRegistry Abstract class, encapsulates the Spring Cloud Registry implementation class based on the SPI of Spring Cloud DiscoveryClient and Spring Cloud Registry.

9.3.1 Construction Method

// SpringCloudRegistry.java

/**
 * ServiceRegistry object
 */
private final ServiceRegistry<Registration> serviceRegistry;

/**
 * DiscoveryClient object
 */
private final DiscoveryClient discoveryClient;

public SpringCloudRegistry(URL url, ServiceRegistry<Registration> serviceRegistry,
                           DiscoveryClient discoveryClient) {
    super(url);
    this.serviceRegistry = serviceRegistry;
    this.discoveryClient = discoveryClient;
}

9.3.2 doRegister

Implement the #doRegister(URL ur) method to perform registration.The code is as follows:

// SpringCloudRegistry.java

@Override
protected void doRegister(URL url) {
    // <1>Get serviceName
    final String serviceName = getServiceName(url);
    // <2>Create Registration Object
    final Registration registration = createRegistration(serviceName, url);
    // <3>Register with service Registry
    serviceRegistry.register(registration);
}
  • <1>, get serviceName.For example: providers:dubbo:org.springframework.cloud.alibaba.dubbo.service.EchoService:1.0.0.The code is as follows:

    // SpringCloudRegistry.java
    
    public static String getServiceName(URL url) {
        // Get classification
        String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
        // Get ServiceName
        return getServiceName(url, category);
    }
    
    private static void appendIfPresent(StringBuilder target, URL url, String parameterName) {
        String parameterValue = url.getParameter(parameterName);
        appendIfPresent(target, parameterValue);
    }
    
    private static final String SERVICE_NAME_SEPARATOR = ":";
    private static void appendIfPresent(StringBuilder target, String parameterValue) {
        if (StringUtils.hasText(parameterValue)) {
            target.append(SERVICE_NAME_SEPARATOR).append(parameterValue);
        }
    }
    
  • At <2>, create the DubboRegistration object.The code is as follows:

    // SpringCloudRegistry.java
    
    private Registration createRegistration(String serviceName, URL url) {
        return new DubboRegistration(createServiceInstance(serviceName, url));
    }
    
    private ServiceInstance createServiceInstance(String serviceName, URL url) {
        // Append default category if absent
        // Get Properties
        String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
        URL newURL = url.addParameter(Constants.CATEGORY_KEY, category);
        newURL = newURL.addParameter(Constants.PROTOCOL_KEY, url.getProtocol());
        String ip = NetUtils.getLocalHost(); // IP
        int port = newURL.getParameter(Constants.BIND_PORT_KEY, url.getPort()); // port
        // Create DefaultServiceInstance object
        DefaultServiceInstance serviceInstance = new DefaultServiceInstance(serviceName, ip, port, false);
        serviceInstance.getMetadata().putAll(new LinkedHashMap<>(newURL.getParameters()));
        return serviceInstance;
    }
    
  • At <3>, call the ServiceRegistry#register(R registration) method and register it with the service Registry.This way, Dubbo is registered with Spring Cloud Registry.

9.3.3 doUnregister

Implement the #doUnregister(URL url) method and unregister it.The code is as follows:

// SpringCloudRegistry.java

@Override
protected void doUnregister(URL url) {
    // Get serviceName
    final String serviceName = getServiceName(url);
    // Create Registration Object
    final Registration registration = createRegistration(serviceName, url);
    // Unregister from serviceRegistry
    this.serviceRegistry.deregister(registration);
}

9.3.4 doSubscribe

Implement the #doSubscribe(URL url, NotifyListener listener) method to execute the subscription.The code is as follows:

// SpringCloudRegistry.java

@Override
protected void doSubscribe(URL url, NotifyListener listener) {
    // <1>Get the serviceName array
    List<String> serviceNames = getServiceNames(url, listener);
    // <2>Execute Subscription
    doSubscribe(url, listener, serviceNames);
}
  • At <1>, get the serviceName array.The code is as follows:

    // SpringCloudRegistry.java
    
    private List<String> getServiceNames(URL url, NotifyListener listener) {
        // Administrator, ignoring temporarily
        if (isAdminProtocol(url)) {
            scheduleServiceNamesLookup(url, listener);
            return getServiceNamesForOps(url);
        } else {
            return doGetServiceNames(url);
        }
    }
    
    private List<String> doGetServiceNames(URL url) {
        // Get the category array
        String[] categories = getCategories(url);
        // Create the serviceName array and get it
        List<String> serviceNames = new ArrayList<String>(categories.length);
        for (String category : categories) {
            final String serviceName = getServiceName(url, category);
            serviceNames.add(serviceName);
        }
        return serviceNames;
    }
    
  • At <2>, call the #doSubscribe (final URL url, final NotifyListener listener, final List <String> serviceNames) method to execute the subscription.The code is as follows:

    // SpringCloudRegistry.java
    
    private void doSubscribe(final URL url, final NotifyListener listener, final List<String> serviceNames) {
        for (String serviceName : serviceNames) {
            // <2.1>Get the ServiceInstance array
            List<ServiceInstance> serviceInstances = discoveryClient.getInstances(serviceName);
            // <2.2>Notify Subscribers
            notifySubscriber(url, listener, serviceInstances);
            // TODO Support Update notification event
        }
    }
    
    • Traverse the serviceNames array, one by one.
    • At <2.1>, call the DiscoveryClient#getInstances(String serviceId) method to get an array of ServiceInstance s.
    • At <2.2>, call the #notifySubscriber (URL url, NotifyListener listener, List <ServiceInstance> serviceInstances) method to notify the subscriber.For a detailed analysis, see 「notifySubscriber」 .

9.3.4.1 notifySubscriber

#notifySubscriber (URL url, NotifyListener listener, List <ServiceInstance> serviceInstances) method to notify subscribers.The code is as follows:

// SpringCloudRegistry.java

private void notifySubscriber(URL url, NotifyListener listener, List<ServiceInstance> serviceInstances) {
    // <1>Filter out unhealthy Dubbo services
    List<ServiceInstance> healthyInstances = new LinkedList<ServiceInstance>(serviceInstances);
    // <1> Healthy Instances
    filterHealthyInstances(healthyInstances);
    // <2>Create URL Array
    List<URL> urls = buildURLs(url, healthyInstances);
    // <3>Notify Subscribers
    this.notify(url, listener, urls);
}
  • At <1>, call the #filterHealthyInstances (Collection <ServiceInstance> instances) method to filter out unhealthy Dubbo services.The code is as follows:

    // SpringCloudRegistry.java
    
    private void filterHealthyInstances(Collection<ServiceInstance> instances) {
        filter(instances, new Filter<ServiceInstance>() {
            @Override
            public boolean accept(ServiceInstance data) {
                // TODO check the details of status
    //                return serviceRegistry.getStatus(new DubboRegistration(data)) != null;
                return true;
            }
        });
    }
    
    private <T> void filter(Collection<T> collection, Filter<T> filter) {
        // remove if not accept
        collection.removeIf(data -> !filter.accept(data));
    }
    
    private interface Filter<T> {
    
        /**
         * Tests whether or not the specified data should be accepted.
         *
         * @param data The data to be tested
         * @return <code>true</code> if and only if <code>data</code>
         * should be accepted
         */
        boolean accept(T data);
    
    }
    
    • As you can see from the TODO check the details of status, it is not currently implemented.Subsequently, it may be filtered based on the status.
  • At <2>, call the #buildURLs (URL consumerURL, Collection <ServiceInstance> serviceInstances) method to convert the ServiceInstance array into an array of URLs.The code is as follows:

    // SpringCloudRegistry.java
    
    private List<URL> buildURLs(URL consumerURL, Collection<ServiceInstance> serviceInstances) {
        // Service Instances is empty, returning an empty array
        if (serviceInstances.isEmpty()) {
            return Collections.emptyList();
        }
        // If serviceInstances are not empty, the corresponding URL objects are built one by one and added to the urls to return
        List<URL> urls = new LinkedList<>();
        for (ServiceInstance serviceInstance : serviceInstances) {
            // Building URL Objects
            URL url = buildURL(serviceInstance);
            if (UrlUtils.isMatch(consumerURL, url)) {
                urls.add(url);
            }
        }
        return urls;
    }
    
    private URL buildURL(ServiceInstance serviceInstance) {
        return new URL(serviceInstance.getMetadata().get(Constants.PROTOCOL_KEY),
                serviceInstance.getHost(),
                serviceInstance.getPort(),
                serviceInstance.getMetadata());
    }
    
  • At <3>, call the parent #notify (URL url, NotifyListener listener, List <URL> urls) method to notify the subscriber.

9.3.5 doUnsubscribe

Implement the #doUnsubscribe(URL url, NotifyListener listener) method to unsubscribe.The code is as follows:

// SpringCloudRegistry.java

@Override
protected void doUnsubscribe(URL url, NotifyListener listener) {
    // Ignore management side
    if (isAdminProtocol(url)) {
        shutdownServiceNamesLookup();
    }
}

Posted by Mathy on Wed, 04 Sep 2019 18:25:03 -0700