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(); } } |
- The DubboServiceMetadataRepository object was created with @Import(DubboServiceMetadataRepository.class).For a detailed analysis, see 「7.5 DubboServiceMetadataRepository」 .
- #metadataConfigService() method, which creates the NacosMetadataConfigService Bean object.For a detailed analysis, see 「7.4.1 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.
- After each Dubbo service is exposed, a ServiceBeanExportedEvent event is published.In Exact Dubbo Source Analysis - Note Configuration Of 「5.3.3 onApplicationEvent」 In, we have a detailed analysis.
- Receiving a ServiceBeanExportedEvent event, the method calls the MetadataResolver#resolveServiceRestMetadata(ServiceBean serviceBean) method, obtains the ServiceRestMetadata collection of the ServiceBean, and adds it to the serviceRestMetadata.For a detailed analysis, see 「7.5 DubboServiceBeanMetadataResolver」 .
-
#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(); } } |