集成SpringCloud
1. 概述
本文,我们来分享 Spring Cloud Alibaba Dubbo 项目的源码解析,看看 Dubbo 是如何集成到 Spring Cloud 中的。
Spring Cloud 和 Dubbo 一直不是竞争的关系,胖友要好好理解哟。
目前 Spring Cloud Alibaba Dubbo 暂时没有文档,不过不用担心,艿艿会先教你搭建一个示例,同时它也是我们后面用来调试的示例。
2. 调试环境搭建
在读源码之前,我们当然是先把调试环境搭建起来。
2.1 依赖工具
- JDK :1.8+
- Maven
- IntelliJ IDEA
2.2 源码拉取
从官方仓库 https://github.com/spring-cloud-incubator/spring-cloud-alibabaFork 出属于自己的仓库。为什么要 Fork ?既然开始阅读、调试源码,我们可能会写一些注释,有了自己的仓库,可以进行自由的提交。
使用 IntelliJ IDEA 从 Fork 出来的仓库拉取代码。拉取完成后,Maven 会下载依赖包,可能会花费一些时间,耐心等待下。
考虑到方便,我们直接使用
spring-cloud-alibaba-dubbo
项目提供的示例,就在它的
test
测试目录下,如下图所示:
示例项目
另外,本文使用的 spring-cloud-alibaba 版本是 0.2.2.BUILD-SNAPSHOT 。
2.3 启动 Nacos
因为后面我们会使用 Nacos 作为注册中心和配置中心,所以需要启动它。具体的,参考艿艿在 《Nacos 实现原理与源码解析系统 —— 精品合集》 的 「2. 快速开始」 小节。
2.4 启动示例
右键 DubboSpringCloudBootstrap 类的 #main(String[] args) 方法,直接运行即可。 如果你没有看到任何异常输出,说明就已经成功了。
另外,这个示例,即做了服务消费者,又做了服务提供者。
下面,让我们让我们逐步解释示例中的每个类和配置文件。
2.4.1 bootstrap.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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 相关的配置,可以无视,因为我们使用的是 Nacos 作为注册中心。
- spring.application.name ,配置了应用名。
- spring.cloud.nacos.discovery.server-addr ,配置了 Nacos 作为注册中心。
- spring.cloud.nacos.config.server-addr ,配置了 Nacos 作为配置中心。
2.4.2 application.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
dubbo:
scan:
base-packages: org.springframework.cloud.alibaba.dubbo.service # 扫描指定包,生成对应的 @Service 和 @Reference Bean 对象
protocols:
dubbo:
name: dubbo # Dubbo 协议
port: 12345 # Dubbo 协议的端口
rest:
name: rest # REST 协议
port: 9090 # REST 协议的端口
server: netty # 使用 Netty 作为 HTTP Server
registry:
address: spring-cloud://nacos # Dubbo 注册中心
feign:
hystrix:
enabled: true # 开启 Hystrix 功能,可以熔断落
server:
port: 8080 # HTTP API 端口
- 每个配置,看看其后的配置文件。
2.4.3 EchoService
org.springframework.cloud.alibaba.dubbo.service.EchoService ,EchoService 接口。代码如下:
1
2
3
4
5
6
7
8
// EchoService.java
public interface EchoService {
String echo(String message);
String plus(int a, int b);
}
- 熟悉不能在熟悉的 Dubbo Service 接口的代码~
2.4.4 DefaultEchoService
org.springframework.cloud.alibaba.dubbo.service.DefaultEchoService ,实现 EchoService 接口,默认的 EchoService 实现者,服务提供者。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 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;
}
}
- @Service(version = “1.0.0”, protocol = {“dubbo”, “rest”}) 注解,提供 Dubbo 和 Rest 两种协议的服务。
2.4.5 DubboSpringCloudBootstrap
org.springframework.cloud.alibaba.dubbo.bootstrap.DubboSpringCloudBootstrap ,示例的 Spring Boot 启动器。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// DubboSpringCloudBootstrap.java
@EnableDiscoveryClient // 开启注册发现
@EnableAutoConfiguration // 开启自动配置
@EnableFeignClients // 开启 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);
}
}
- @EnableDiscoveryClient 注解,用于开启注册发现 Client 的功能。
- @EnableAutoConfiguration 注解,用于开启自动配置。
- @EnableFeignClients 注解,开启 Feign Client 。在 spring-cloud-alibaba-dubbo 项目中,使用 Feign 作为服务消费者。
- @RestController 注解,后续我们会看到这个类中会提供基于 Spring MVC 的 HTTP API 接口。
- echoService 属性,使用 @Reference(version = “1.0.0”) 注解,引入 Dubbo 服务。这个方式,就是我们原先在 Dubbo 中就使用的。
- feignEchoService 属性,使用标准的 Feign Client 作为服务消费者,它使用 RestTemplate 调用的是 Dubbo 提供的 Rest 接口。代码如下:
1
2
3
4
5
6
7
8
// DubboSpringCloudBootstrap.java
@FeignClient("spring-cloud-alibaba-dubbo")
public interface FeignEchoService {
@GetMapping(value = "/echo")
String echo(@RequestParam("message") String message);
}
- dubboFeignEchoService 属性,也使用标准的 Feign Client 作为服务消费者,它调用 Dubbo 调用的是 Dubbo 提供的 Dubbo 接口。代码如下:
1
2
3
4
5
6
7
8
9
// DubboSpringCloudBootstrap.java
@FeignClient("spring-cloud-alibaba-dubbo")
public interface DubboFeignEchoService {
@GetMapping(value = "/echo")
@DubboTransported
String echo(@RequestParam("message") String message);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 相比 </font>echoService</font> 属性,它在方法上,增加了 </font>org.springframework.cloud.alibaba.dubbo.annotation.@DubboTransported</font> 注解。- 可能会有胖友好奇,具体是如何实现的呢?本文我们一起来揭晓~```
---
```java
// 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"));
};
}
- 声明了 ApplicationRunner Bean 对象,在 Spring Boot 启动完成,直接发起相应的调用。它的目的是,看看三种调用方式,是否都正常。如果没有报错,说明都是 OK 的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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);
}
- 声明了三个 Spring MVC HTTP API ,分别调用三个不同的服务消费者。
- 这就是为什么 DubboSpringCloudBootstrap 有 @RestController 注解,且配置文件中配置了 server.port=8080 。
至此,我们已经完整看过了 Spring Cloud Alibaba Dubbo 的示例,可以愉快的开始调试了。
3. 项目结构一览
本文主要分享 spring-cloud-alibaba-dubbo 的 项目结构。希望通过本文能让胖友对 spring-cloud-alibaba-dubbo 的整体项目有个简单的了解。
项目结构一览
3.1 代码统计
这里先分享一个小技巧。笔者在开始源码学习时,会首先了解项目的代码量。
第一种方式,使用 IDEA Statistic 插件,统计整体代码量。
Statistic 统计代码量
- 我们可以粗略的看到,整个 Spring Cloud Alibaba 的代码量在 16000 行。这其中还包括单元测试,示例等等代码。
- /(ㄒoㄒ)/~~ 显然,目前这个插件没办法很方便的统计出,我们想要看到的 spring-cloud-alibaba-dubbo 的代码量。
第二种方式,使用 Shell 脚本命令逐个 Maven 模块统计 。
| 一般情况下,笔者使用 find . -name “*.java” | xargs cat | grep -v -e ^$ -e ^//.*$ | wc -l 。这个命令只过滤了部分注释,所以相比 IDEA Statistic 会偏多。 |
当然,考虑到准确性,胖友需要手动 cd 到每个 Maven 项目的 src/main/java 目录下,以达到排除单元测试的代码量。
Shell 脚本统计代码量
统计完后,发现代码量是不多的。
3.2 annotation 包
annotation 包,62 行代码,提供 @EnableFeignClients 注解。
3.3 autoconfigure 包
autoconfigure 包,172 行代码,提供 Spring Boot 自动配置 Spring Cloud Alibaba Dubbo 。
3.4 context 包
context 包,35 行代码,目前仅有 DubboServiceRegistrationApplicationContextInitializer 类,先不解释哈~
3.5 metadata 包
metadata 包,904 行代码,实现将 Spring Cloud Alibaba Dubbo 的服务的元数据,存储到配置中心。这样,后续使用 @DubboTransported 注解的 Dubbo 调用时,因为会使用到 Dubbo的泛化调用 ,有了服务的元数据,就可以愉快的调用了。
3.6 openfeign 包
openfeign 包,305 行代码,实现将 Dubbo 集成到 OpenFeign 中。
3.7 registry 包
registry 包,478 行代码,实现 Dubbo 使用 Spring Cloud Service 注册中心体系。可能这么说有点抽象,我们可以回过头看看 「2.4.2 application.yaml」 中看到,有个神奇的 dubbo.protocols.registry.address=spring-cloud://nacos 配置。
emm~哈哈哈,等会看具体代码,会更加明白。
4. annotation 包
4.1 @DubboTransported
org.springframework.cloud.alibaba.dubbo.annotation.@DubboTransported 注解,表名调用时,使用 Dubbo 作为底层 RPC 调用。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 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}) // 支持类、方法
@Documented
public @interface DubboTransported {
/**
* The protocol of Dubbo transport whose value could be used the placeholder "dubbo.transport.protocol"
*
* 使用的 Dubbo 协议,默认为 "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"
*
* 使用的集群容错方式,默认为 "failover"
*
* @return the default protocol is "failover"
*/
String cluster() default "${dubbo.transport.cluster:failover}";
}
- protocol 属性,使用的 Dubbo 协议,默认为 “dubbo” 。
- cluster 属性,使用 使用的集群容错方式,默认为 “failover” 。
- 可标记在类或者方法上。
5. autoconfigure 包
在 META-INF/spring.factories 文件中,声明了三个自动配置类。代码如下:
1
2
3
4
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
- 下面,我们逐个来瞅瞅。
5.1 DubboMetadataAutoConfiguration
org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboMetadataAutoConfiguration ,Dubbo 元数据(Metadata)相关 Bean 的自动配置类。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
// DubboMetadataAutoConfiguration.java
@Configuration
@Import(DubboServiceMetadataRepository.class) // 创建了 DubboServiceMetadataRepository Bean 对象
public class DubboMetadataAutoConfiguration {
@Bean // 创建了 NacosMetadataConfigService Bean 对象
@ConditionalOnBean(NacosConfigProperties.class)
public MetadataConfigService metadataConfigService() {
return new NacosMetadataConfigService();
}
}
- 通过 「7.5 DubboServiceMetadataRepository」 @Import(DubboServiceMetadataRepository.class) ,创建了 DubboServiceMetadataRepository 对象。详细解析,见 。
- #metadataConfigService()「7.4.1 NacosMetadataConfigService」 方法,创建了 NacosMetadataConfigService Bean 对象。详细解析,见 。
5.2 DubboOpenFeignAutoConfiguration
org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboOpenFeignAutoConfiguration ,Dubbo OpenFeign 相关 Bean 的自动配置类。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// DubboOpenFeignAutoConfiguration.java
@ConditionalOnClass(value = Feign.class) // 存在 Feign 类的时候,即存在 feign 依赖
@AutoConfigureAfter(FeignAutoConfiguration.class) // 在 FeignAutoConfiguration 配置类之后初始化
@Configuration
public class DubboOpenFeignAutoConfiguration {
@Value("${spring.application.name}")
private String currentApplicationName;
@Bean // 创建 DubboServiceBeanMetadataResolver 对象
@ConditionalOnMissingBean
public MetadataResolver metadataJsonResolver(ObjectProvider<Contract> contract) {
return new DubboServiceBeanMetadataResolver(currentApplicationName, contract);
}
@Bean // 创建 TargeterBeanPostProcessor 对象
public TargeterBeanPostProcessor targeterBeanPostProcessor(Environment environment,
DubboServiceMetadataRepository dubboServiceMetadataRepository) {
return new TargeterBeanPostProcessor(environment, dubboServiceMetadataRepository);
}
}
- #metadataJsonResolver(…)「7.2 MetadataResolver」 方法,创建 DubboServiceBeanMetadataResolver Bean 对象。详细解析,见 。
- #targeterBeanPostProcessor(…)「8.1 TargeterBeanPostProcessor」 方法,创建 TargeterBeanPostProcessor Bean 对象。详细解析,见 。
5.3 DubboRestMetadataRegistrationAutoConfiguration
org.springframework.cloud.alibaba.dubbo.autoconfigure.DubboRestMetadataRegistrationAutoConfiguration ,自动配置两个 Spring 事件监听器,将 Dubbo Rest 元数据(Metadata)注册到配置中心。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// DubboRestMetadataRegistrationAutoConfiguration.java
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true) // 要求有 "spring.cloud.service-registry.auto-registration.enabled=true" ,或者不配置。
@ConditionalOnBean(value = { // 要求存在 MetadataResolver、MetadataConfigService Bean 对象
MetadataResolver.class,
MetadataConfigService.class })
@AutoConfigureAfter(value = {DubboMetadataAutoConfiguration.class}) // 在 DubboMetadataAutoConfiguration 配置类之后初始化
@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)集合
*/
private final Set<ServiceRestMetadata> serviceRestMetadata = new LinkedHashSet<>();
@Autowired // 默认情况下,来自 DubboOpenFeignAutoConfiguration 注册的 DubboServiceBeanMetadataResolver Bean 对象
private MetadataResolver metadataResolver;
@Autowired // 默认情况下,来自 DubboMetadataAutoConfiguration 注册的 NacosMetadataConfigService Bean 对象
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 属性,Dubbo Rest Service 方法的元数据(Metadata)集合。
- #recordRestMetadata(ServiceBeanExportedEvent) 方法,监听 ServiceBeanExportedEvent 事件。
- 在每个 Dubbo 服务暴露后,会发布 ServiceBeanExportedEvent 事件。在 《精尽 Dubbo 源码分析 —— 注解配置》 的 「5.3.3 onApplicationEvent」 中,我们有过详细解析。
- 在接收到 ServiceBeanExportedEvent 事件,该方法会调用 「7.5 DubboServiceBeanMetadataResolver」 MetadataResolver#resolveServiceRestMetadata(ServiceBean serviceBean) 方法,获得该 ServiceBean 的 ServiceRestMetadata 集合,添加到 serviceRestMetadata 中。详细解析,见 。
- #registerRestMetadata(InstancePreRegisteredEvent) 方法,监听 InstancePreRegisteredEvent 事件。
- InstancePreRegisteredEvent 事件,在 Spring Cloud 应用注册到注册中心之前,会触发该事件。详细的,可以看看 《spring-cloud-commons reference》「2.2.1 ServiceRegistry Auto-Registration」 文章的 小节。现在,可以不看~
- 在接收到 InstancePreRegisteredEvent 事件,该方法会调用 MetadataConfigService#publishServiceRestMetadata(String serviceName, Set serviceRestMetadata) 方法,将每个 Dubbo 服务的 ServiceRestMetadata 元数据集合,发布(存储)到配置中心。
- 这样,后续的 Dubbo 泛化调用,就有 Dubbo 服务的元数据咧。
因为本小节讲的都是自动配置类,胖友可能会略有懵逼。不要慌,我们继续往下撸。
对咧,最好一边调试,一边看。
6. context 包
在 META-INF/spring.factories 文件中,声明了三个自动配置类。代码如下:
1
plain org.springframework.context.ApplicationContextInitializer=\ org.springframework.cloud.alibaba.dubbo.context.DubboServiceRegistrationApplicationContextInitializer
6.1 DubboServiceRegistrationApplicationContextInitializer
org.springframework.cloud.alibaba.dubbo.context.DubboServiceRegistrationApplicationContextInitializer ,实现 ApplicationContextInitializer 接口,将 applicationContext 设置到 SpringCloudRegistryFactory.applicationContext 静态属性。代码如下:
1
plain // 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 包
在看具体代码之前,我们先在 Nacos 的配置中心界面,看看什么是 Dubbo Metadata 。如下图所示:
Dubbo Metadata
可能这样还是不够清理,我们来看一个 Dubbo Service Metadata 更具体的示例,如下 JSON 串:
1
plain { "name" : "providers:dubbo:org.springframework.cloud.alibaba.dubbo.service.EchoService:1.0.0", // dubbo 协议 "meta" : [ { // 一个 Dubbo 方法 "method" : { // 方法信息 "name" : "echo", "returnType" : "java.lang.String", "params" : [ { "index" : 0, "name" : "message", "type" : "java.lang.String" } ] }, "request" : { // 请求信息 "method" : "GET", "url" : "/echo", "queries" : { "message" : [ "{message}" ] }, "headers" : { } }, "indexToName" : { "0" : [ "message" ] } }, { // 一个 Dubbo 方法 "method" : { // 方法信息 "name" : "plus", "returnType" : "java.lang.String", "params" : [ { "index" : 0, "name" : "a", "type" : "int" }, { "index" : 1, "name" : "b", "type" : "int" } ] }, "request" : { // 请求信息 "method" : "POST", "url" : "/plus", "queries" : { "a" : [ "{a}" ], "b" : [ "{b}" ] }, "headers" : { } }, "indexToName" : { "0" : [ "a" ], "1" : [ "b" ] } } ] }
-
7.1 Metadata 类
在 metadata 包的根目录,一共有 6 个 Metadata 类。如下:
- ServiceRestMetadata
- RestMethodMetadata
- MethodMetadata
- MethodParameterMetadata
- RequestMetadata
- DubboTransportedMethodMetadata
7.1.1 ServiceRestMetadata
org.springframework.cloud.alibaba.dubbo.metadata.ServiceRestMetadata ,Service Rest Metadata 。代码如下:
1
plain // ServiceRestMetadata.java public class ServiceRestMetadata { /** * 服务名 */ private String name; /** * Rest 方法元数据 */ private Set<RestMethodMetadata> meta; // ... 省略 setting/getting 方法 }
7.1.2 RestMethodMetadata
org.springframework.cloud.alibaba.dubbo.metadata.RestMethodMetadata ,Rest Method Metadata 。代码如下:
1
plain // RestMethodMetadata.java public class RestMethodMetadata { /** * 方法元数据 */ private MethodMetadata method; /** * 请求元数据 */ private RequestMetadata request; /** * TODO */ private Map<Integer, Collection<String>> indexToName; // ... 省略 setting/getting 方法 }
7.1.2.1 MethodMetadata
org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata ,Method Metadata 。代码如下:
1
plain // ServiceRestMetadata.java public class MethodMetadata { /** * 方法名 */ private String name; /** * 返回类型 */ private String returnType; /** * 方法参数元数据的数组 */ private List<MethodParameterMetadata> params; /** * 方法 */ @JsonIgnore // 不存储到配置中心 private Method method; public MethodMetadata() { this.params = new LinkedList<>(); } public MethodMetadata(Method method) { this.name = method.getName(); // 获得返回类型 this.returnType = ClassUtils.getName(method.getReturnType()); // 初始化 params this.params = initParameters(method); this.method = method; } private List<MethodParameterMetadata> initParameters(Method method) { // 获得参数数量 int parameterCount = method.getParameterCount(); // 如果参数不存在,则返回空数组 if (parameterCount < 1) { return Collections.emptyList(); } // 创建 MethodParameterMetadata 数组 List<MethodParameterMetadata> params = new ArrayList<>(parameterCount); Parameter[] parameters = method.getParameters(); for (int i = 0; i < parameterCount; i++) { // 获得 Parameter 对象 Parameter parameter = parameters[i]; // 转换成 MethodParameterMetadata 对象 MethodParameterMetadata param = toMethodParameterMetadata(i, parameter); // 添加到 params 中 params.add(param); } return params; } private MethodParameterMetadata toMethodParameterMetadata(int index, Parameter parameter) { // 创建 MethodParameterMetadata 对象 MethodParameterMetadata metadata = new MethodParameterMetadata(); metadata.setIndex(index); // 方法参数的位置 metadata.setName(parameter.getName()); // 方法参数的名字 metadata.setType(parameter.getType().getTypeName()); // 方法参数的类型 return metadata; } // ... 省略 setting/getting 方法 }
7.1.2.1.1 MethodParameterMetadata
org.springframework.cloud.alibaba.dubbo.metadata.MethodParameterMetadata ,Method Parameter Metadata 。代码如下:
1
plain // MethodParameterMetadata.java public class MethodParameterMetadata { /** * 方法参数的位置 */ private int index; /** * 方法参数的名字 */ private String name; /** * 方法参数的类型 */ private String type; // ... 省略 setting/getting 方法 }
7.1.2.2 RequestMetadata
org.springframework.cloud.alibaba.dubbo.metadata.RequestMetadata ,Request Metadata 。代码如下:
1
plain // RequestMetadata.java public class RequestMetadata { /** * 方法名 */ private String method; /** * URL 路径 */ private String url; private Map<String, Collection<String>> queries; private Map<String, Collection<String>> headers; public RequestMetadata() { } // 将 RequestTemplate 对象,转换成 RequestMetadata 对象 public RequestMetadata(RequestTemplate requestTemplate) { this.method = requestTemplate.method(); this.url = requestTemplate.url(); this.queries = requestTemplate.queries(); this.headers = requestTemplate.headers(); } // ... 省略 setting/getting 方法 }
7.1.2.3 DubboTransportedMethodMetadata
org.springframework.cloud.alibaba.dubbo.metadata.DubboTransportedMethodMetadata ,继承 MethodMetadata 类, @DubboTransported 注解对应的 MethodMetadata 对象。代码如下:
1
plain // DubboTransportedMethodMetadata.java public class DubboTransportedMethodMetadata extends MethodMetadata { /** * Dubbo 协议 */ private String protocol; /** * Dubbo 容错策略 */ private String cluster; // ... 省略 setting/getting 方法 }
7.2 MetadataResolver
org.springframework.cloud.alibaba.dubbo.metadata.resolver.MetadataResolver ,Metadata Resolver 元数据解析器接口。代码如下:
1
plain // MetadataResolver.java /** * The REST metadata resolver */ public interface MetadataResolver { /** * Resolve the {@link ServiceRestMetadata} {@link Set set} from {@link ServiceBean} * * 解析指定 ServiceBean 的 ServiceRestMetadata 集合 * * @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} * * 解析指定类的 ServiceRestMetadata 集合 * * @param targetType {@link Class target type} * @return non-null {@link Set} */ Set<RestMethodMetadata> resolveMethodRestMetadata(Class<?> targetType); }
7.2.1 DubboServiceBeanMetadataResolver
DubboServiceBeanMetadataResolver Bean 对象,在 「5.2 DubboOpenFeignAutoConfiguration」 中被创建。
org.springframework.cloud.alibaba.dubbo.metadata.resolver.DubboServiceBeanMetadataResolver ,实现 MetadataResolver、BeanClassLoaderAware、SmartInitializingSingleton 接口,基于 Dubbo ServiceBean 的 MetadataResolver 实现类。
1
plain // DubboServiceBeanMetadataResolver.java /** * The metadata resolver for {@link Feign} for {@link ServiceBean Dubbo Service Bean} in the provider side. */
7.2.1.1 构造方法
1
plain // DubboServiceBeanMetadataResolver.java private static final String[] CONTRACT_CLASS_NAMES = { "feign.jaxrs2.JAXRS2Contract", "org.springframework.cloud.openfeign.support.SpringMvcContract", }; /** * 当前应用名 * * 不过,目前暂时并未用该参数 */ private final String currentApplicationName; /** * 当前类加载器 */ private ClassLoader classLoader; private final ObjectProvider<Contract> contract; /** * Feign Contract 数组 * * https://www.jianshu.com/p/6582f8319f72 */ private Collection<Contract> contracts; public DubboServiceBeanMetadataResolver(String currentApplicationName, ObjectProvider<Contract> contract) { this.currentApplicationName = currentApplicationName; this.contract = contract; }
- 具体的每个属性,我们下面一个一个来看。
7.2.1.2 afterSingletonsInstantiated
实现 #afterSingletonsInstantiated() 方法,初始化 contracts 属性。代码如下:
1
plain // DubboServiceBeanMetadataResolver.java @Override public void afterSingletonsInstantiated() { // <1> 创建 Feign Contract 数组 LinkedList<Contract> contracts = new LinkedList<>(); // Add injected Contract if available, for example SpringMvcContract Bean under Spring Cloud Open Feign // <2.1> 如果 contract 存在,则添加到 contracts 中 contract.ifAvailable(contracts::add); // <2.2> 遍历 CONTRACT_CLASS_NAMES 数组,创建对应的 Contract 对象,添加到 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> 赋值给 contracts 中 this.contracts = Collections.unmodifiableCollection(contracts); }
- <1> 处,创建 Feign Contract 数组 contracts 。
- <2.1> 处,如果 contract 存在,则添加到 contracts 中。一般情况下, contract 不存在,所以可以暂时无视。
- <2.2> 处,遍历 CONTRACT_CLASS_NAMES 数组,创建对应的 Contract 对象,添加到 contracts 中。一般来说,都会存在。涉及代码如下:
1
plain // DubboServiceBeanMetadataResolver.java // 判断指定 className 类是否存在 private boolean isClassPresent(String className) { return ClassUtils.isPresent(className, classLoader); } // 加载 contractClassName 对应的 Contract 实现类 private Class<?> loadContractClass(String contractClassName) { return ClassUtils.resolveClassName(contractClassName, classLoader); } // 创建 Contract 对象 private Contract createContract(Class<?> contractClassName) { return (Contract) BeanUtils.instantiateClass(contractClassName); }
- <3> 处,赋值给 this.contracts 中。
7.2.1.3 resolveMethodRestMetadata
实现 #resolveMethodRestMetadata(Class<?> targetType) 方法,解析指定类的 ServiceRestMetadata 集合。代码如下:
1
plain // DubboServiceBeanMetadataResolver.java @Override public Set<RestMethodMetadata> resolveMethodRestMetadata(Class<?> targetType) { // <1> 获得 Method 集合 List<Method> feignContractMethods = selectFeignContractMethods(targetType); // <2> 转换成 RestMethodMetadata 集合 return contracts.stream() // 遍历 contracts 数组 .map(contract -> contract.parseAndValidatateMetadata(targetType)) // <2.1> 返回目标类型的 Feign MethodMetadata 数组 .flatMap(Collection::stream) .map(methodMetadata -> resolveMethodRestMetadata(methodMetadata, targetType, feignContractMethods)) // <2.2> 将 Feign MethodMetadata 转换成 RestMethodMetadata 对象 .collect(Collectors.toSet()); // 转换成 Set }
- <1> 处,调用 #selectFeignContractMethods(Class<?> targetType) 方法,获得 Method 集合。代码如下:
1
plain // 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<>(); // 遍历目标的方法 for (Method method : targetType.getMethods()) { // 忽略 if (method.getDeclaringClass() == Object.class || // Object 声明的方法,例如说 equals 方法 (method.getModifiers() & Modifier.STATIC) != 0 || // 静态方法 Util.isDefault(method)) { // Feign 默认方法 continue; } methods.add(method); } return methods; }
- <2> 处,转换成 RestMethodMetadata 集合。
- <2.1> 处,调用 Contract#parseAndValidatateMetadata() 方法,返回目标类型的 Feign MethodMetadata 数组。这块代码属于 Feign 的,我们先不细调,看一个结果的示例。如下图:
结果- 这里有一点要注意,因为 CONTRACT_CLASS_NAMES 中,即有 JAXRS2Contract 类,又有 SpringMvcContract 类,所以要求添加 JSR311 的注解,也要添加 Spring MVC 的注解。举个例子:
1
plain // DefaultEchoService.java @Service(version = "1.0.0", protocol = {"dubbo", "rest"}) @RestController // Spring MVC 注解 @Path("/") // JSR311 注解 public class DefaultEchoService implements EchoService { @Override @GetMapping(value = "/echo" // consumes = MediaType.APPLICATION_JSON_VALUE, // produces = MediaType.APPLICATION_JSON_UTF8_VALUE ) // Spring MVC 注解 @Path("/echo") // JSR311 注解 @GET // JSR311 注解 // @Consumes("application/json") // @Produces("application/json;charset=UTF-8") public String echo(@RequestParam // Spring MVC 注解 @QueryParam("message") String message) { // JSR311 注解 System.out.println(message); return RpcContext.getContext().getUrl() + " [echo] : " + message; } }
1
2
3
4
5
6
7
8
9
* 不过艿艿暂时不太理解,为什么这么设计。有知道的胖友,麻烦教育下,嘻嘻~```
- <2.2>
处,调用
#resolveMethodRestMetadata(MethodMetadata methodMetadata, Class<?> targetType, List feignContractMethods)
方法,将 Feign MethodMetadata 转换成 RestMethodMetadata 对象。代码如下:
```text
plain // DubboServiceBeanMetadataResolver.java protected RestMethodMetadata resolveMethodRestMetadata(MethodMetadata methodMetadata, // Feign MethodMetadata Class<?> targetType, List<Method> feignContractMethods) { // 获得 configKey 。例如说:DefaultEchoService#echo(String) String configKey = methodMetadata.configKey(); // 获得匹配的 Method Method feignContractMethod = getMatchedFeignContractMethod(targetType, feignContractMethods, configKey); // 创建 RestMethodMetadata 对象,并设置其属性 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; // 遍历 Method 集合 for (Method method : methods) { // 获得该方法的 configKey String configKey = Feign.configKey(targetType, method); // 如果相等,则进行返回。 if (expectedConfigKey.equals(configKey)) { matchedMethod = method; break; } } return matchedMethod; }
1
2
3
4
5
6
7
8
- 对象转换的代码,简单瞅瞅即可明白。```
### 7.2.1.4 resolveServiceRestMetadata
实现 #resolveServiceRestMetadata(ServiceBean serviceBean) 方法,解析指定 ServiceBean 的 ServiceRestMetadata 集合。代码如下:
```text
plain // DubboServiceBeanMetadataResolver.java @Override public Set<ServiceRestMetadata> resolveServiceRestMetadata(ServiceBean serviceBean) { // <1.1> 获得应用的 Bean 对象 Object bean = serviceBean.getRef(); // <1.2> 获得 Bean 类型 Class<?> beanType = bean.getClass(); // <1.3> 解析 Bean 类型对应的 RestMethodMetadata 集合 Set<RestMethodMetadata> methodRestMetadata = resolveMethodRestMetadata(beanType); // <2.1> 创建 ServiceRestMetadata 数组 Set<ServiceRestMetadata> serviceRestMetadata = new LinkedHashSet<>(); // <2.2> 获得 ServiceBean 暴露的 URL 集合 List<URL> urls = serviceBean.getExportedUrls(); // <2.3> 遍历 URL 集合,将 RestMethodMetadata 集合,封装成 ServiceRestMetadata 对象,然后添加到 serviceRestMetadata 中,最后返回。 urls.stream() .map(SpringCloudRegistry::getServiceName) .forEach(serviceName -> { ServiceRestMetadata metadata = new ServiceRestMetadata(); metadata.setName(serviceName); metadata.setMeta(methodRestMetadata); serviceRestMetadata.add(metadata); }); return serviceRestMetadata; }
- <1.3>「7.2.1.3 resolveMethodRestMetadata」 处,调用 #resolveMethodRestMetadata(Class<?> targetType) 方法,解析 Bean 类型对应的 RestMethodMetadata 集合。即,我们在 解析的方法。
- <2.1> 处,获得 ServiceBean 暴露的 URL 集合。例如说,本文的 DefaultEchoService 示例,就暴露了 dubbo:// 和 rest:// 两种协议的 URL 。
- <2.3> 处,遍历 URL 集合,将 RestMethodMetadata 集合,封装成 ServiceRestMetadata 对象,然后添加到 serviceRestMetadata 中,最后返回。此处的 SpringCloudRegistry::getServiceName 代码段,就是调用 SpringCloudRegistry#getServiceName(URL url) 方法,获得 Dubbo 服务名。例如说: providers:dubbo:org.springframework.cloud.alibaba.dubbo.service.EchoService:1.0.0 。
至此,Dubbo Service 元数据的解析,就已经完成了。后续,我们会看到两个方面的内容:
- 1、将元数据存储到配置中心。这样,服务消费者才能共享到该部分的数据。
- 2、服务消费者基于 Dubbo Service 元数据,可以使用 Dubbo 发起泛化调用。
当然,如果不使用 Dubbo 发起泛化调用,是不需要这部分 Dubbo Service 元数据的。为什么呢?胖友好好思考一波~
7.3 DubboTransportedMethodMetadataResolver
org.springframework.cloud.alibaba.dubbo.metadata.resolver.DubboTransportedMethodMetadataResolver ,解析 @DubboTransported 注解的 MethodMetadata 数据。代码如下:
1
plain // DubboTransportedMethodMetadataResolver.java public Map<DubboTransportedMethodMetadata, RequestMetadata> resolve(Class<?> targetType) { // <1> 获得指定类的 DubboTransportedMethodMetadata 集合 Set<DubboTransportedMethodMetadata> dubboTransportedMethodMetadataSet = resolveDubboTransportedMethodMetadataSet(targetType); // <2> 获得指定类的 RequestMetadata 映射。其中,KEY 为 configKey Map<String, RequestMetadata> requestMetadataMap = resolveRequestMetadataMap(targetType); // <3> 转换成 DubboTransportedMethodMetadata 和 RequestMetadata 的映射 return dubboTransportedMethodMetadataSet .stream() .collect(Collectors.toMap(methodMetadata -> methodMetadata, methodMetadata -> requestMetadataMap.get(Feign.configKey(targetType, methodMetadata.getMethod())) )); }
- <1> 处,调用 #resolveDubboTransportedMethodMetadataSet(Class<?> targetType) 方法,获得指定类的 DubboTransportedMethodMetadata 集合。代码如下:
1
plain // DubboTransportedMethodMetadataResolver.java protected Set<DubboTransportedMethodMetadata> resolveDubboTransportedMethodMetadataSet(Class<?> targetType) { // The public methods of target interface Method[] methods = targetType.getMethods(); // 创建 DubboTransportedMethodMetadata 数组 Set<DubboTransportedMethodMetadata> methodMetadataSet = new LinkedHashSet<>(); // 遍历方法 for (Method method : methods) { // 如果有 @DubboTransported 注解 DubboTransported dubboTransported = resolveDubboTransported(method); // ① // 如果有,则创建成 DubboTransportedMethodMetadata 对象,并添加到 methodMetadataSet 中 if (dubboTransported != null) { // 创建 ② DubboTransportedMethodMetadata methodMetadata = createDubboTransportedMethodMetadata(method, dubboTransported); // 添加 methodMetadataSet.add(methodMetadata); } } return methodMetadataSet; } // ① private DubboTransported resolveDubboTransported(Method method) { // 先从方法上,获得 @DubboTransported 注解 DubboTransported dubboTransported = AnnotationUtils.findAnnotation(method, DUBBO_TRANSPORTED_CLASS); // 如果获得不到,则从类上,获得 @DubboTransported 注解 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) { // 创建 DubboTransportedMethodMetadata 对象 DubboTransportedMethodMetadata methodMetadata = new DubboTransportedMethodMetadata(method); // 解析属性,并设置到 methodMetadata 中 String protocol = propertyResolver.resolvePlaceholders(dubboTransported.protocol()); String cluster = propertyResolver.resolvePlaceholders(dubboTransported.cluster()); methodMetadata.setProtocol(protocol); methodMetadata.setCluster(cluster); return methodMetadata; }
- <2> 处,调用 #resolveRequestMetadataMap(Class<?> targetType) 方法,获得指定类的 RequestMetadata 映射。其中,KEY 为 configKey 。代码如下:
1
plain // DubboTransportedMethodMetadataResolver.java private Map<String, RequestMetadata> resolveRequestMetadataMap(Class<?> targetType) { return contract.parseAndValidatateMetadata(targetType) // 获得指定类的 Feign MethodMetadata 集合 .stream().collect(Collectors.toMap(feign.MethodMetadata::configKey, this::requestMetadata)); // 创建 RequestMetadata 对象 } private RequestMetadata requestMetadata(feign.MethodMetadata methodMetadata) { return new RequestMetadata(methodMetadata.template()); }
- <3> 处,转换成 DubboTransportedMethodMetadata 和 RequestMetadata 的映射。
后续,这个类会被 「8.1 TargeterInvocationHandler」 所调用 。
7.4 MetadataConfigService
给服务提供者使用。
org.springframework.cloud.alibaba.dubbo.metadata.service.MetadataConfigService ,元数据配置服务接口,即可以从配置中心,读取和写入元数据。代码如下:
1
plain // MetadataConfigService.java public interface MetadataConfigService { /** * 发布指定服务的 Rest 元数据 * * @param serviceName 服务名 * @param serviceRestMetadata ServiceRestMetadata 集合 */ void publishServiceRestMetadata(String serviceName, Set<ServiceRestMetadata> serviceRestMetadata); /** * 获得指定服务的 Rest 元数据 * * @param serviceName 服务名 * @return ServiceRestMetadata 集合 */ Set<ServiceRestMetadata> getServiceRestMetadata(String serviceName); }
7.4.1 NacosMetadataConfigService
org.springframework.cloud.alibaba.dubbo.metadata.service.NacosMetadataConfigService ,实现 MetadataConfigService 接口,基于 Nacos 作为配置中心的实现类。
7.4.1.1 构造方法
1
plain // NacosMetadataConfigService.java /** * ObjectMapper ,使用 Jackson 序列化和反序列化 */ private final ObjectMapper objectMapper = new ObjectMapper(); /** * NacosConfigProperties 对象,用于获得 {@link #configService} */ @Autowired private NacosConfigProperties nacosConfigProperties; private ConfigService configService; @PostConstruct public void init() { // 初始化 configService 属性 this.configService = nacosConfigProperties.configServiceInstance(); // 开启 JSON 格式化 this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT); }
7.4.1.2 publishServiceRestMetadata
实现 #publishServiceRestMetadata(String serviceName, Set serviceRestMetadata) 方法,代码如下:
1
plain // NacosMetadataConfigService.java @Override public void publishServiceRestMetadata(String serviceName, Set<ServiceRestMetadata> serviceRestMetadata) { // 获得 Nacos dataId ① String dataId = getServiceRestMetadataDataId(serviceName); // 将 ServiceRestMetadata 集合序列化成 json 字符串 ② String json = writeValueAsString(serviceRestMetadata); // 写入到 Nacos 配置中心 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
实现 #getServiceRestMetadata(String serviceName) 方法,代码如下:
1
plain // NacosMetadataConfigService.java @Override public Set<ServiceRestMetadata> getServiceRestMetadata(String serviceName) { Set<ServiceRestMetadata> metadata; // 获得 Nacos dataId String dataId = getServiceRestMetadataDataId(serviceName); try { // 从 Nacos 配置中心,读取 json 字符串 String json = configService.getConfig(dataId, DEFAULT_GROUP, 1000 * 3); // 将 json 字符串,反序列化成 ServiceRestMetadata 集合 metadata = objectMapper.readValue(json, TypeFactory.defaultInstance().constructCollectionType(LinkedHashSet.class, ServiceRestMetadata.class)); } catch (Exception e) { throw new RuntimeException(e); } return metadata; }
7.5 DubboServiceMetadataRepository
给服务消费者使用。
org.springframework.cloud.alibaba.dubbo.metadata.repository.DubboServiceMetadataRepository ,Dubbo Service Metadata 仓库。通过该类,服务消费者就可以获得每个对应 Dubbo 服务的 ReferenceBean 对象~
7.5.1 构造方法
1
plain // DubboServiceMetadataRepository.java /** * RequestMetadata 和 ReferenceBean 的映射 * * Key is application name * Value is Map<RequestMetadata, ReferenceBean<GenericService>> */ private Map<String, Map<RequestMetadata, ReferenceBean<GenericService>>> referenceBeansRepository = new HashMap<>(); /** * RequestMetadata 和 MethodMetadata 的映射 * * Key is application name */ private Map<String, Map<RequestMetadata, MethodMetadata>> methodMetadataRepository = new HashMap<>(); @Autowired private MetadataConfigService metadataConfigService;
- 看看每个属性上的注释哟。
- 有一点要注意, Key is application name ,表示首个 KEY 是 Spring Cloud(Spring Boot)应用名。
- 但是,一个 Dubbo 应用中,可以有多个 Dubbo Service ,所以,就有了第二个 Map ,例如 Map<RequestMetadata, ReferenceBean 。
- 当然,此处是按照 RequestMetadata 为维度,即每个 Dubbo Service 方法对应的请求信息。因为,每个请求路径的 URL 是唯一的,所以这么做是 OK 的。相当于说,把每个 Dubbo Service 的方法,平铺开。
7.5.2 updateMetadata
#updateMetadata(String serviceName) 方法,初始化指定 serviceName 的元数据。代码如下:
1
plain // DubboServiceMetadataRepository.java public void updateMetadata(String serviceName) { // <1.1> 获得 serviceName 对应的 RequestMetadata 和 ReferenceBean 的映射 Map<RequestMetadata, ReferenceBean<GenericService>> genericServicesMap = referenceBeansRepository.computeIfAbsent(serviceName, k -> new HashMap<>()); // <1.2> 获得 serviceName 对应的 RequestMetadata 和 MethodMetadata 的映射 Map<RequestMetadata, MethodMetadata> methodMetadataMap = methodMetadataRepository.computeIfAbsent(serviceName, k -> new HashMap<>()); // <1.3> 获得 serviceName 对应的 ServiceRestMetadata 集合 Set<ServiceRestMetadata> serviceRestMetadataSet = metadataConfigService.getServiceRestMetadata(serviceName); // <2> 遍历 ServiceRestMetadata 集合,创建对应的 ReferenceBean ,获得对应的 MethodMetadata 对象 for (ServiceRestMetadata serviceRestMetadata : serviceRestMetadataSet) { // <2.1> 创建对应的 ReferenceBean ReferenceBean<GenericService> referenceBean = adaptReferenceBean(serviceRestMetadata); // <2.2> 遍历 RestMethodMetadata 集合,添加到 genericServicesMap 和 methodMetadataMap 中,进行缓存 serviceRestMetadata.getMeta().forEach(restMethodMetadata -> { RequestMetadata requestMetadata = restMethodMetadata.getRequest(); genericServicesMap.put(requestMetadata, referenceBean); methodMetadataMap.put(requestMetadata, restMethodMetadata.getMethod()); }); } }
- <1.3>「7.4 MetadataConfigService」 处,调用 MetadataConfigService#getServiceRestMetadata(String serviceName) 方法,获得指定 serviceName 的 ServiceRestMetadata 集合。此处,我们就使用上了 。
- <2> 处,遍历 ServiceRestMetadata 集合,创建对应的 ReferenceBean ,获得对应的 MethodMetadata 对象。
- <2.1> 处,调用 #adaptReferenceBean(ServiceRestMetadata serviceRestMetadata) 方法,创建 ReferenceBean 对象。代码如下:
1
plain // DubboServiceMetadataRepository.java private ReferenceBean<GenericService> adaptReferenceBean(ServiceRestMetadata serviceRestMetadata) { // 获得相应的属性 String dubboServiceName = serviceRestMetadata.getName(); String[] segments = SpringCloudRegistry.getServiceSegments(dubboServiceName); String interfaceName = SpringCloudRegistry.getServiceInterface(segments); String version = SpringCloudRegistry.getServiceVersion(segments); String group = SpringCloudRegistry.getServiceGroup(segments); // 创建 ReferenceBean 对象,并设置相关属性 ReferenceBean<GenericService> referenceBean = new ReferenceBean<GenericService>(); referenceBean.setGeneric(true); referenceBean.setInterface(interfaceName); referenceBean.setVersion(version); referenceBean.setGroup(group); return referenceBean; }
1
2
3
4
5
6
7
8
* 注意哦,此时创建的 ReferenceBean 是 Dubbo 的泛化引用。- <2.2></font> 处,遍历 RestMethodMetadata 集合,添加到 </font>genericServicesMap</font> 和 </font>methodMetadataMap</font> 中,进行缓存。```
### 7.5.3 getReferenceBean
#getReferenceBean(String serviceName, RequestMetadata requestMetadata) 方法,获得指定应用的指定 RequestMetadata 对应的 ReferenceBean 对象。代码如下:
```text
plain 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
#getMethodMetadata(String serviceName, RequestMetadata requestMetadata) 方法,获得指定应用的指定 RequestMetadata 对应的 MethodMetadata 对象。代码如下:
1
plain // 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 小结
至此,我们看完了 metadata 包下的所有代码。因为本小节更多是元数据的解析、存储、读取,不涉及到具体的使用,所以会略微有点懵逼。但是,到下一节 openfeign 后,Feign 通过泛化调用对应的 Dubbo 服务,就会使用上元数据了。此时,我们就可以把整个流程打通。
8. openfeign 包
本小节,我们来瞅瞅,Dubbo 是如何和 Feign 进行集成的。
8.1 TargeterBeanPostProcessor
org.springframework.cloud.alibaba.dubbo.openfeign.TargeterBeanPostProcessor ,实现 BeanPostProcessor、BeanClassLoaderAware 接口,处理类型为 openfeign Targeter 的 Bean ,创建其动态代理,从而能够将 Dubbo 集成到 Openfeign 中。代码如下:
1
plain // 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> 获得 Bean 的类 Class<?> beanClass = ClassUtils.getUserClass(bean.getClass()); // <2> 获得 openfeign Targeter 接口 Class<?> targetClass = ClassUtils.resolveClassName(TARGETER_CLASS_NAME, classLoader); // <3> 如果实现 openfeign Targeter 接口,则创建动态代理 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; } }
- 在分析具体代码之前,胖友先看下 《Spring Cloud Alibaba Sentinel 整合 Feign 的设计实现》「Feign 的执行过程」 的 小节。因为艿艿看的时候也不是很了解 Feign 的内部运转机制,也是参考这个小节读懂这部分代码的。
- <1> 处,获得 Bean 的类。根据上面推荐的文章,我们可以知道,此时返回的是 HystrixTargeter 或 DefaultTargeter 类。
- <2> 处,获得 openfeign Targeter 接口。
- <3>「8.2 TargeterInvocationHandler」 处,如果实现 openfeign Targeter 接口,则创建动态代理。其中,传入的处理器是 TargeterInvocationHandler 对象。详细解析,见 。
8.2 TargeterInvocationHandler
org.springframework.cloud.alibaba.dubbo.openfeign.TargeterInvocationHandler ,会拦截 Targeter 的 #target(FeignClientFactoryBean factory, Builder feign, FeignContext context, HardCodedTarget target) 方法,根据条件,创建不同的代理对象。代码如下:
如果不理解 Targeter 的话,请再看下 《Spring Cloud Alibaba Sentinel 整合 Feign 的设计实现》 的 「Feign 的执行过程」 小节。
1
plain // 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> 先调用原有 target 方法,返回默认的代理对象 // Execute Targeter#target method first method.setAccessible(true); // Get the default proxy object Object defaultProxy = method.invoke(bean, args); // Create Dubbo Proxy if required // 如果符合创建 Dubbo 代理对象,则创建 Dubbo 代理对象。 // 否则,使用默认的 defaultProxy 代理 return createDubboProxyIfRequired(feignContext, target, defaultProxy); } // ... 省略下面要讲解的方法 }
- 为了让胖友更加好理解,我们来看下这个方法被调用时的截图:
调用图 - <1> 处,先调用原有 target 方法,返回默认的代理对象。
- <2> 处,调用 #createDubboProxyIfRequired(FeignContext feignContext, Target target, Object defaultProxy) 方法,如果符合创建 Dubbo 代理对象,则创建 Dubbo 代理对象。否则,使用默认的 defaultProxy 代理。那么问题就来了,条件是什么呢?有 @DubboTransported 注解,且从配置中心拉取不到服务提供者的元数据。 因为,没有服务提供者的元数据,我们也无法使用 Dubbo 的泛化调用呀。
- so ,我们继续往下看。
8.2.1 createDubboProxyIfRequired
#createDubboProxyIfRequiredcreateDubboProxyIfRequired(FeignContext feignContext, Target target, Object defaultProxy) 方法,根据条件,创建对应的代理对象。代码如下:
1
plain // TargeterInvocationHandler.java private Object createDubboProxyIfRequired(FeignContext feignContext, Target target, Object defaultProxy) { // <1> 尝试创建 DubboInvocationHandler DubboInvocationHandler dubboInvocationHandler = createDubboInvocationHandler(feignContext, target, defaultProxy); // <2.1> 如果未创建成功,说明不符合条件,则返回默认的 defaultProxy 代理 if (dubboInvocationHandler == null) { return defaultProxy; } // <2.2> 如果创建成功,说明符合条件,则创建使用 dubboInvocationHandler 的动态代理 return Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, dubboInvocationHandler); }
- <1> 处,调用 #createDubboInvocationHandler(FeignContext feignContext, Target target, Object defaultFeignClientProxy) 方法,尝试创建 DubboInvocationHandler。
- <2.1> 处,如果未创建成功,说明不符合条件,则返回默认的 defaultProxy 代理。
- <2.2> 处,如果创建成功,说明符合条件,则创建使用 dubboInvocationHandler 的动态代理。
8.2.2 createDubboInvocationHandler
#createDubboInvocationHandler(FeignContext feignContext, Target target, Object defaultFeignClientProxy) 方法,创建 DubboInvocationHandler 对象。代码如下:
未来这块的逻辑,会被抽取到 org.springframework.cloud.alibaba.dubbo.openfeign.DubboInvocationHandlerFactory 类中。
1
plain // 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> 获得 Feign Contract Contract contract = feignContext.getInstance(serviceName, Contract.class); // <1.2> 创建 DubboTransportedMethodMetadataResolver 对象 DubboTransportedMethodMetadataResolver resolver = new DubboTransportedMethodMetadataResolver(environment, contract); // <1.3> 解析指定类,获得其 DubboTransportedMethodMetadata 和 RequestMetadata 的映射 Map<DubboTransportedMethodMetadata, RequestMetadata> methodRequestMetadataMap = resolver.resolve(targetType); // <1.4> 如果为空,则返回,说明不符合条件 if (methodRequestMetadataMap.isEmpty()) { // @DubboTransported method was not found return null; } // Update Metadata // <2> 初始化指定 `serviceName` 的元数据。此处,会从配置中心,获得元数据 dubboServiceMetadataRepository.updateMetadata(serviceName); Map<Method, org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata> methodMetadataMap = new HashMap<>(); Map<Method, GenericService> genericServicesMap = new HashMap<>(); // <3> 遍历 methodRequestMetadataMap 集合,初始化其 GenericService methodRequestMetadataMap.forEach((dubboTransportedMethodMetadata, requestMetadata) -> { // <3.1.1> 获得 ReferenceBean 对象,并初始化其属性 ReferenceBean<GenericService> referenceBean = dubboServiceMetadataRepository.getReferenceBean(serviceName, requestMetadata); referenceBean.setProtocol(dubboTransportedMethodMetadata.getProtocol()); referenceBean.setCluster(dubboTransportedMethodMetadata.getCluster()); // <3.1.2> 添加到 genericServicesMap 中 genericServicesMap.put(dubboTransportedMethodMetadata.getMethod(), referenceBean.get()); // <3.2.1> 获得 MethodMetadata 对象 org.springframework.cloud.alibaba.dubbo.metadata.MethodMetadata methodMetadata = dubboServiceMetadataRepository.getMethodMetadata(serviceName, requestMetadata); // <3.2.2> 添加到 methodMetadataMap 中 methodMetadataMap.put(dubboTransportedMethodMetadata.getMethod(), methodMetadata); }); // <4.1> 获得默认的 defaultFeignClientProxy 中,默认的 InvocationHandler 对象 InvocationHandler defaultFeignClientInvocationHandler = Proxy.getInvocationHandler(defaultFeignClientProxy); // <4.2> 创建 DubboInvocationHandler 对象 return new DubboInvocationHandler(genericServicesMap, methodMetadataMap, defaultFeignClientInvocationHandler); }
- <1.1> 处,获得 Feign Contract 。
- <1.2> 处,创建 DubboTransportedMethodMetadataResolver 对象。
- <1.3>「7.3 DubboTransportedMethodMetadata」 处,调用 DubboTransportedMethodMetadataResolver#resolve(Class<?> targetType) 解析指定类,获得其 DubboTransportedMethodMetadata 和 RequestMetadata 的映射。此处,我们就把 给串起来了。
- <1.4> 处,如果为空,则返回,说明不符合条件。此处,我们来抛一个问题,如果一个类里,多个方法中,有部分方法没有 @DubboTransported 注解,那么会不会创建 DubboInvocationHandler 呢?答案是肯定的。那么此时,就会有 @DubboTransported 注解的方法,使用 Dubbo 进行调用,没有 @DubboTransported 注解的方法,还是选择原有 Feign 提供的方式(例如说 RestTemplate)进行调用。
- <2>「7.5 DubboServiceMetadataRepository」 处,调用 DubboServiceMetadataRepository#updateMetadata(String serviceName) 方法,初始化指定 serviceName 的元数据(此时,会从配置中心,获得元数据)。此处,我们就把 给串起来了。
- <3> 处,遍历 methodRequestMetadataMap 集合,初始化其 GenericService 。
- <3.1.1> 处,获得 ReferenceBean 对象,并初始化其属性。
- <3.1.2> 处,添加到 genericServicesMap 中。
- <3.2.1> 处,获得 MethodMetadata 对象。
- <3.2.2> 处,添加到 methodMetadataMap 中。
- <4.1> 处,获得默认的 defaultFeignClientProxy 中,默认的 InvocationHandler 对象。为什么需要它呢?因为,一个类中,可能有没有 @DubboTransported 注解的方法。
- <4.2> 处,创建 DubboInvocationHandler 对象。
8.3 DubboInvocationHandler
org.springframework.cloud.alibaba.dubbo.openfeign.DubboInvocationHandler ,实现 InvocationHandler 接口,Dubbo InvocationHandler 实现类。代码如下:
1
plain // 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 { // 获得 GenericService 对象 GenericService genericService = genericServicesMap.get(method); // 获得 MethodMetadata 对象 MethodMetadata methodMetadata = this.methodMetadata.get(method); // <1> 情况一,如果任一不存在,使用默认的 defaultInvocationHandler if (genericService == null || methodMetadata == null) { return defaultInvocationHandler.invoke(proxy, method, args); } // 情况二,执行泛化调用 String methodName = methodMetadata.getName(); // 方法名 String[] parameterTypes = methodMetadata .getParams() .stream() .map(MethodParameterMetadata::getType) .toArray(String[]::new); // 参数类型 return genericService.$invoke(methodName, parameterTypes, args); } }
- <1> 处,如果任一不存在,使用默认的 defaultInvocationHandler ,即无 @DubboTransported 注解的方法。
- <2> 处,执行泛化调用,即有 @DubboTransported 注解的方法。
8.4 小结
至此,整个服务消费者调用的流程,我们已经串联起来了。因为本文是按照 package 包分层来写,所以连贯性会相对比较差。因此,需要胖友自己在调试一下哈。
9. registry 包
本小节,我们来瞅瞅,Dubbo 是如何和 Spring Cloud 注册中心进行集成的。
在 2.4.2 application.yaml 中,我们可以看到,注册中心使用的是 dubbo.registry.address: spring-cloud://nacos 。
9.1 Registration
org.springframework.cloud.alibaba.dubbo.registry.DubboRegistration ,实现 Spring Cloud Registration 接口,Dubbo Registration 实现类。代码如下:
1
plain // 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
在 com.alibaba.dubbo.registry.RegistryFactory 中,声明了一个 SpringCloudRegistryFactory 拓展。如下:
1
plain spring-cloud=org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegistryFactory
- 前缀为 “spring-cloud” 。即,和我们配置的 dubbo.registry.address: spring-cloud://nacos 能够对应上。
org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegistryFactory ,实现 Dubbo RegistryFactory 接口,创建对 SpringCloudRegistry 注册中心。代码如下:
1
plain // SpringCloudRegistryFactory.java public class SpringCloudRegistryFactory implements RegistryFactory { private static ApplicationContext applicationContext; @Override public Registry getRegistry(URL url) { // <1> 获得 ServiceRegistry 对象 ServiceRegistry<Registration> serviceRegistry = applicationContext.getBean(ServiceRegistry.class); // <2> 获得 DiscoveryClient 对象 DiscoveryClient discoveryClient = applicationContext.getBean(DiscoveryClient.class); // <3> 创建 SpringCloudRegistry 对象 return new SpringCloudRegistry(url, serviceRegistry, discoveryClient); } public static void setApplicationContext(ApplicationContext applicationContext) { SpringCloudRegistryFactory.applicationContext = applicationContext; } }
- <1> 处,获得 ServiceRegistry 对象。此处,如果我们使用 Nacos 作为注册中心,那么返回的就是 org.springframework.cloud.alibaba.nacos.registry.NacosServiceRegistry 对象。
- <2> 处,获得 DiscoveryClient 对象。此处,如果我们使用 Nacos 作为注册中心,那么返回的 Composite DiscoveryClient 对象,包含 org.springframework.cloud.alibaba.nacos.NacosDiscoveryClient 对象。
- 上述两个变量,如下图所示:
变量 - <3>「9.3 SpringCloudRegistry」 处,创建 SpringCloudRegistry 对象。详细解析,见 。
9.3 SpringCloudRegistry
本小节,建立在胖友看过 《精尽 Dubbo 源码分析 —— 注册中心(一)之抽象 API》 文章。
org.springframework.cloud.alibaba.dubbo.registry.SpringCloudRegistry ,继承 Dubbo FailbackRegistry 抽象类,基于 Spring Cloud DiscoveryClient、SpringCloudRegistry 的 API ,封装出 Spring Cloud Registry 实现类。
9.3.1 构造方法
1
plain // SpringCloudRegistry.java /** * ServiceRegistry 对象 */ private final ServiceRegistry<Registration> serviceRegistry; /** * DiscoveryClient 对象 */ 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
实现 #doRegister(URL ur) 方法,执行注册。代码如下:
1
plain // SpringCloudRegistry.java @Override protected void doRegister(URL url) { // <1> 获得 serviceName final String serviceName = getServiceName(url); // <2> 创建 Registration 对象 final Registration registration = createRegistration(serviceName, url); // <3> 注册到 serviceRegistry 中 serviceRegistry.register(registration); }
- <1> 处,获得 serviceName 。例如: providers:dubbo:org.springframework.cloud.alibaba.dubbo.service.EchoService:1.0.0 。代码如下:
1
plain // SpringCloudRegistry.java public static String getServiceName(URL url) { // 获得分类 String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY); // 获得 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); } }
- <2> 处,创建 DubboRegistration 对象。代码如下:
1
plain // 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 // 获得属性 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()); // 端口 // 创建 DefaultServiceInstance 对象 DefaultServiceInstance serviceInstance = new DefaultServiceInstance(serviceName, ip, port, false); serviceInstance.getMetadata().putAll(new LinkedHashMap<>(newURL.getParameters())); return serviceInstance; }
- <3> 处,调用 ServiceRegistry#register(R registration) 方法,注册到 serviceRegistry 中。这样,Dubbo 就注册到 Spring Cloud Registry 中。
9.3.3 doUnregister
实现 #doUnregister(URL url) 方法,取消注册。代码如下:
1
plain // SpringCloudRegistry.java @Override protected void doUnregister(URL url) { // 获得 serviceName final String serviceName = getServiceName(url); // 创建 Registration 对象 final Registration registration = createRegistration(serviceName, url); // 取消注册从 serviceRegistry 中 this.serviceRegistry.deregister(registration); }
9.3.4 doSubscribe
实现 #doSubscribe(URL url, NotifyListener listener) 方法,执行订阅。代码如下:
1
plain // SpringCloudRegistry.java @Override protected void doSubscribe(URL url, NotifyListener listener) { // <1> 获得 serviceName 数组 List<String> serviceNames = getServiceNames(url, listener); // <2> 执行订阅 doSubscribe(url, listener, serviceNames); }
- <1> 处,获得 serviceName 数组。代码如下:
1
plain // SpringCloudRegistry.java private List<String> getServiceNames(URL url, NotifyListener listener) { // 管理端,暂时无视 if (isAdminProtocol(url)) { scheduleServiceNamesLookup(url, listener); return getServiceNamesForOps(url); } else { return doGetServiceNames(url); } } private List<String> doGetServiceNames(URL url) { // 获得 category 数组 String[] categories = getCategories(url); // 创建 serviceName 数组,并进行获得 List<String> serviceNames = new ArrayList<String>(categories.length); for (String category : categories) { final String serviceName = getServiceName(url, category); serviceNames.add(serviceName); } return serviceNames; }
- <2> 处,调用 #doSubscribe(final URL url, final NotifyListener listener, final List serviceNames) 方法,执行订阅。代码如下:
1
plain // SpringCloudRegistry.java private void doSubscribe(final URL url, final NotifyListener listener, final List<String> serviceNames) { for (String serviceName : serviceNames) { // <2.1> 获得 ServiceInstance 数组 List<ServiceInstance> serviceInstances = discoveryClient.getInstances(serviceName); // <2.2> 通知订阅者 notifySubscriber(url, listener, serviceInstances); // TODO Support Update notification event } }
1
2
3
4
5
6
7
8
- 遍历 </font>serviceNames</font> 数组,逐个处理。- <2.1></font> 处,调用 </font>DiscoveryClient#getInstances(String serviceId)</font> 方法,获得 ServiceInstance 数组。- <2.2></font> 处,调用 </font>#notifySubscriber(URL url, NotifyListener listener, List<ServiceInstance> serviceInstances)</font> 方法,通知订阅者。详细解析,见 </font>[「notifySubscriber」](http://svip.iocoder.cn/Dubbo/spring-cloud-integration/#) 。```
### 9.3.4.1 notifySubscriber
#notifySubscriber(URL url, NotifyListener listener, List serviceInstances) 方法,通知订阅者。代码如下:
```text
plain // SpringCloudRegistry.java private void notifySubscriber(URL url, NotifyListener listener, List<ServiceInstance> serviceInstances) { // <1> 过滤掉非健康的 Dubbo 服务 List<ServiceInstance> healthyInstances = new LinkedList<ServiceInstance>(serviceInstances); // <1> Healthy Instances filterHealthyInstances(healthyInstances); // <2> 创建 URL 数组 List<URL> urls = buildURLs(url, healthyInstances); // <3> 通知订阅者 this.notify(url, listener, urls); }
- <1> 处,调用 #filterHealthyInstances(Collection instances) 方法,过滤掉非健康的 Dubbo 服务。代码如下:
1
plain // 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); }
1
2
3
4
5
6
7
8
9
- 从 </font>TODO check the details of status</font> 可以看出,目前暂时未实现。后续,可能会根据状态进行过滤。```
- <2>
处,调用
#buildURLs(URL consumerURL, Collection serviceInstances)
方法,将 ServiceInstance 数组,转换成 URL 数组。代码如下:
```text
plain // SpringCloudRegistry.java private List<URL> buildURLs(URL consumerURL, Collection<ServiceInstance> serviceInstances) { // serviceInstances 为空,返回空数组 if (serviceInstances.isEmpty()) { return Collections.emptyList(); } // serviceInstances 非空,则逐个构建对应的 URL 对象,添加到 urls 中进行返回 List<URL> urls = new LinkedList<>(); for (ServiceInstance serviceInstance : serviceInstances) { // 构建 URL 对象 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()); }
- <3> 处,调用父 #notify(URL url, NotifyListener listener, List urls) 方法,通知订阅者。
9.3.5 doUnsubscribe
实现 #doUnsubscribe(URL url, NotifyListener listener) 方法,取消订阅。代码如下:
1
plain // SpringCloudRegistry.java @Override protected void doUnsubscribe(URL url, NotifyListener listener) { // 忽略管理端 if (isAdminProtocol(url)) { shutdownServiceNamesLookup(); } }
666. 彩蛋
大体是这样,如果有些细节没写到位,欢迎知识星球留言。
比较有趣的,还是泛化调用那一块。




