Dubbo Spring Cloud 逆向分析服务注册事件变化的处理过程
这篇介绍了如何从接收事件的方法逆向推出完整的事件处理过程,这个方法适合在解决具体问题或学习源码时,倒着把处理过程理顺。
起因
原来用的 Spring Boot + Dubbo 开发架构,在架构中有一个特殊的功能用到了 NotifyListener
来监听指定接口变化的信息。现在要将架构升级为 Spring Cloud Alibaba,升级后发现官方对 NotifyListener
的支持还不完整。
DubboCloudRegistry
类中的如下方法:
@Override
public final void doSubscribe(URL url, NotifyListener listener) {if (isAdminURL(url)) {// TODO in futureif (logger.isWarnEnabled()) {logger.warn("This feature about admin will be supported in the future.");}}else if (isDubboMetadataServiceURL(url)) { // for DubboMetadataServicesubscribeDubboMetadataServiceURLs(url, listener);}else { // for general Dubbo ServicessubscribeURLs(url, listener);}
}
原来用的 ADMIN_PROTOCOL = "admin"
不支持了,因此项目卡在了一个非常重要的功能上。
经过查看 spring-cloud-starter-dubbo
代码,发现一些不完美的替代方案,就是 com.alibaba.cloud.dubbo.registry.event
包下提供的事件类:
ServiceInstancePreRegisteredEvent
ServiceInstanceRegisteredEvent
ServiceInstancesChangedEvent
SubscribedServicesChangedEvent
这里的 ServiceInstancesChangedEvent
可以代替对服务监听的变化:
public class ServiceInstancesChangedEvent extends ApplicationEvent {private final String serviceName;private final List<ServiceInstance> serviceInstances;
为了测试监听的功能,在官方 spring-cloud-dubbo-client-sample
的示例代码中,添加了如下代码:
@EventListener
public void serviceInstancesChangedEventListener(ServiceInstancesChangedEvent event) {logger.info("ServiceName: " + event.getServiceName());for (ServiceInstance instance : event.getServiceInstances()) {logger.info("URI: " + instance.getUri());}
}
在官方示例中运行良好,但是在我自己项目中这么写时,这个方法一直没触发。为了了解这里事件的触发过程,就有了下面的操作。
特别提示
我使用 Zookeeper 作为注册中心,因此其他情况不一定存在本文遇到的问题。
官方示例改为 zookeeper 注册中心,按下面方式修改依赖:
<!-- Spring Cloud Nacos Service Discovery -->
<!-- <dependency>-->
<!-- <groupId>com.alibaba.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>-->
<!-- </dependency>-->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-zookeeper-all</artifactId><exclusions><exclusion><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId></exclusion></exclusions>
</dependency>
<dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><version>3.4.12</version><exclusions><exclusion><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId></exclusion></exclusions>
</dependency>
注释 nacos 依赖,添加 zookeeper 依赖即可。
在 bootstrap.yaml
中修改注册配置:
spring:application:name: spring-cloud-alibaba-dubbo-clientmain:allow-bean-definition-overriding: truecloud:
# nacos:
# username: nacos
# password: nacos
# discovery:
# server-addr: 127.0.0.1:8848zookeeper:connect-string: 127.0.0.1:2181
同样注释 nacos,配置 zookeeper。
按照同样的方式修改服务提供者 DubboSpringCloudServerBootstrap
。调试过程中,通过该服务的启动关闭来触发事件监听。
逆向分析
想要找到为什么能调用,肯定要先在功能正常的官方示例中运行,由于 Spring 框架中的 IOC、AOP以及各种为了扩展性使用的设计模式、解耦和异步调用的逻辑,导致想直接看源码是很难找到联系的。
因此对框架的分析或者学习时,最好的办法就是 DEBUG!!!
断点调试
在监听代码处添加断点,然后DEBUG运行 DubboSpringCloudClientBootstrap
:
完全启动后,在直接运行方式(Run)方式启动 DubboSpringCloudServerBootstrap
,很快就会进入上面项目中的断点,此时断点变量和堆栈信息如下:
变量信息可以看出这个刚刚启动的 DubboSpringCloudServerBootstrap
对应的实例,重点关注线程堆栈信息。浅黄色背景部分的主要代码是 Java 和 Spring 框架的,中间夹杂3行白色的是 Spring Cloud Alibaba 的代码,这3行如下:
dispatchServiceInstancesChangedEvent:175, DubboServiceDiscoveryAutoConfiguration (com.alibaba.cloud.dubbo.autoconfigure)
access$200:108, DubboServiceDiscoveryAutoConfiguration (com.alibaba.cloud.dubbo.autoconfigure)
beforeChildEvent:446, DubboServiceDiscoveryAutoConfiguration$ZookeeperConfiguration (com.alibaba.cloud.dubbo.autoconfigure)
点开上面 dispatchServiceInstancesChangedEvent:175
对应的代码和堆栈变量信息如下:
可以看到这个事件就是在这里创建的。似乎一下子就找到头了…
事实远非如此,我们继续找事件的源头。
再点第三行 beforeChildEvent:446
查看,如下:
这里可以看到接收到了 Zookeeper 的 TreeCacheEvent
事件,在当前方法中,有如下注解:
@Before("execution(void org.springframework.cloud.zookeeper.discovery \
.ZookeeperServiceWatch.childEvent(..)) && args(client,event)")
这里通过 AOP 监听了 ZookeeperServiceWatch
类的 childEvent
方法:
@Override
public void childEvent(CuratorFramework client, TreeCacheEvent event)throws Exception {if (event.getType().equals(TreeCacheEvent.Type.NODE_ADDED)|| event.getType().equals(TreeCacheEvent.Type.NODE_REMOVED)|| event.getType().equals(TreeCacheEvent.Type.NODE_UPDATED)) {long newCacheChange = this.cacheChange.incrementAndGet();this.publisher.publishEvent(new HeartbeatEvent(this, newCacheChange));}
}
这里能监听到 Zookeeper 节点的变化信息,当前方法下的线程堆栈信息如下:
beforeChildEvent:446, DubboServiceDiscoveryAutoConfiguration$ZookeeperConfiguration (com.alibaba.cloud.dubbo.autoconfigure)
invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:62, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
invoke:566, Method (java.lang.reflect)
invokeAdviceMethodWithGivenArgs:644, AbstractAspectJAdvice (org.springframework.aop.aspectj)
invokeAdviceMethod:626, AbstractAspectJAdvice (org.springframework.aop.aspectj)
before:44, AspectJMethodBeforeAdvice (org.springframework.aop.aspectj)
invoke:55, MethodBeforeAdviceInterceptor (org.springframework.aop.framework.adapter)
proceed:175, ReflectiveMethodInvocation (org.springframework.aop.framework)
proceed:749, CglibAopProxy$CglibMethodInvocation (org.springframework.aop.framework)
invoke:95, ExposeInvocationInterceptor (org.springframework.aop.interceptor)
proceed:186, ReflectiveMethodInvocation (org.springframework.aop.framework)
proceed:749, CglibAopProxy$CglibMethodInvocation (org.springframework.aop.framework)
intercept:691, CglibAopProxy$DynamicAdvisedInterceptor (org.springframework.aop.framework)
childEvent:-1, ZookeeperServiceWatch$$EnhancerBySpringCGLIB$$2aa71b3b (org.springframework.cloud.zookeeper.discovery)
apply:749, TreeCache$2 (org.apache.curator.framework.recipes.cache)
此时可以看到往下最近的就是 java 反射代码,再往下就是 Spring AOP 代码,AOP的尽头就是被代理(AOP)的 childEvent:-1, ZookeeperServiceWatch$$EnhancerBySpringCGLIB$$2aa71b3b
,在往前一行 apply:749, TreeCache$2
就是被代理方法的调用位置:
当前堆栈下的 event
变量值为:
"TreeCacheEvent{type=NODE_ADDED, \
data=ChildData{path='/services/spring-cloud-alibaba-dubbo-server/\
d4cb6d36-235c-42ca-862f-d989ea96c958', /*隐藏部分内容*/ }}"
这就是刚刚注册的实例节点。从代码来看这里是在对所有监听的 this.listeners
迭代执行,从这儿在往上的代码都是 Zookeeper 的内部逻辑了,此时我们关注的目标变成了 ZookeeperServiceWatch
在什么时候添加到 TreeCache
的 listeners
中的?
在 TreeCache
中,对外公开的 listeners
方法只有下面这一个:
public Listenable<TreeCacheListener> getListenable() {return this.listeners;
}
通过 getListenable()
就可以把自己的实现加入到这个列表中,搜索一下所有调用该方法的地方(IDEA 可以在方法名上按鼠标滚轮进行查找,也可以Alt+F7)。
我们想要查找的正是倒数第二个 ZookeeperServiceWatch
,点击方法进入:
@Override
public void onApplicationEvent(InstanceRegisteredEvent<?> event) {this.cache = TreeCache.newBuilder(this.curator, this.properties.getRoot()).build();this.cache.getListenable().addListener(this);try {this.cache.start();}catch (Exception e) {ReflectionUtils.rethrowRuntimeException(e);}
}
这是在一个实现了 ApplicationListener
的接口方法中调用的,Spring 中的事件由于和产生事件的代码进行了解耦,因此这里无法直接看出是哪儿触发的这里,通过直接查找 new InstanceRegisteredEvent
的地方可以找到,有时搜索不一定能找到,还可以使用更简单的方法。
在上面方法加断点,DEBUG重启,等待进入此断点:
进入断点时的堆栈信息如上,红框中的内容是动态代理AOP的内容,直接忽略,查找下面最近一个堆栈 doInvokeListener:172, SimpleApplicationEventMulticaster
,一层层往上找,直到下面的位置:
- 可以看到这里创建
new InstanceRegisteredEvent
- 当前执行代码在抽象类
AbstractAutoServiceRegistration
中start
方法 - 当前的实现类为
ZookeeperAutoServiceRegistration
此处还有另一个【简单分支】,后文分析。
继续看 AbstractAutoServiceRegistration
,发现是从当前类中按照下面的方法顺序调用的:
void onApplicationEvent(WebServerInitializedEvent event)
void bind(WebServerInitializedEvent event)
void start()
事件触发的源头是 WebServerInitializedEvent
,再一层层往上找到如下位置:
继续往上查找最终看到项目中使用的 AnnotationConfigServletWebServerApplicationContext
,当项目使用 SERVLET
时创建的这个类型:
this.webApplicationType
在启动时判断的:
当项目中存在这两个类时,就认为是 SERVLET
。想要有这两个类,最简单的方法就是添加下面的依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
这个依赖会引入 tomcat
,里面包含了 javax.servlet.Servlet
,在 spring-web
中存在 ConfigurableWebApplicationContext
类。
spring-boot-starter-web
部分依赖树如下:
发现问题的原因: 到这里时已经找到了为什么我自己代码中无法监听了,是因为改造的代码中只需要 Dubbo 服务,不需要 Web,因此没有添加上面的依赖,因此就无法将 ZookeeperServiceWatch
添加到监听列表中,也就收不到后续的任何事件了。
简单分支
前面提到了一个简单的分支,对整个过程也到了重要的作用,就是下面的类:
public class ZookeeperAutoServiceRegistrationextends AbstractAutoServiceRegistration<ZookeeperRegistration> {
前面说到通过 AbstractAutoServiceRegistration
中 start
方法将自己添加到了监听列表。这个类本身是通过下面的方式注册的:
当没有实例 "org.springframework.cloud.zookeeper.discovery.ZookeeperLifecycle"
,没有配置 "spring.cloud.service-registry.auto-registration.enabled"
或者配置为 true
时,这个类都会生效。
总结
通过本文逆向分析的过程解决了遇到的问题,同时也了解了事件的部分处理过程。本文分析的这个过程是最重要的,由于 Spring 框架中的 IOC、AOP以及各种为了扩展性使用的设计模式、解耦和异步调用的逻辑,直接通过源码看调用时不知道具体的实现是哪一个, 但是逆向分析时,你看到的都是具体的某一个实例的执行。 通过具体实例的执行,看到的也许只是正向过程的一个分支。通过逆向得到的执行路径,反着来看就是一个正向的路径,知道一个正向路径后就可以在正向分析时,再探索其他的分支路径。通过逆向和正向的反复结合,最终弄清理解代码,解决问题,实现扩展等方面的需求。
Dubbo Spring Cloud 逆向分析服务注册事件变化的处理过程相关推荐
- Spring Cloud之acos服务注册与Dubbo
Spring Cloud之acos服务注册与Dubbo nacos是springcloud的扩展,注册中心功能通过NacosDiscoveryClient 继承DiscoveryClient,在spr ...
- Spring Cloud入门 -- Consul服务注册与发现(Hoxton.SR5版)
Consul简介 Consul 是 HashiCorp 公司推出的开源产品,用于实现分布式系统的服务发现.服务隔离.服务配置,这些功能中的每一个都可以根据需要单独使用,也可以同时使用所有功能.Cons ...
- [Spring-cloud-eureka]使用 gradle, spring boot,Spring cloud Eureka 搭建服务注册中心
2019独角兽企业重金招聘Python工程师标准>>> Spring Cloud Eureka 是 Spring Cloud Netflix 微服务套件的一部分,它基于 Netfli ...
- spring cloud gateway之服务注册与发现
点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 在之前的文章介绍了Spring Cloud Gateway的Predict(断言).Filter( ...
- Spring Cloud入门 -- Eureka服务注册与发现(Hoxton.SR5版)
什么是Spring Cloud Spring Cloud是一系列框架的有序集合.它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册.配置中心.智能路由.消息总 ...
- spring cloud 学习之 服务注册和发现(Eureka)
一:服务注册和发现(Eureka) 1:采用Eureka作为服务注册和发现组件 2:Eureka 项目中 主要在启动类加上 注解@EnableEurekaServer @SpringBootAppli ...
- 【SpringCloud】Spring cloud Alibaba Nacos 服务注册与配置中心
文章目录 1.概述 1.1 为什么叫Nacos 1.2 是什么 1.3.地址 1.4 区别 2.运行 3. 服务提供者 4. 服务消费者 5. 扩展 5.1 Nacos支持AP和CP模式的切换 5.1 ...
- 【SpringCloud】Spring cloud Alibaba Nacos 服务注册与配置中心 命名空间 Data Id
文章目录 1.概述 2.分类配置 2.1 问题1 2.2 问题2 2.3 Namespace +Group+ Data ID三者关系?为什么这么设计? 2.3.1是什么 2.3.2 三 者情况 2.4 ...
- Spring Cloud与微服务学习总结(2)——Spring Cloud相较于Dubbo等RPC服务框架的优势
摘要: 目前,Spring Cloud在国内的知名度并不高,在前阵子的求职过程中,与一些互联网公司的架构师.技术VP或者CTO在交流时,有些甚至还不知道该项目的存在.可能这也与国内阿里巴巴开源服务治理 ...
最新文章
- 【Harvest源码分析】GetFilteredSignal函数
- PHP下实现两种ajax跨域的解决方案之jsonp
- 【渝粤题库】陕西师范大学163202 管理学原理 作业(高起本 专升本)
- python学习精华——成长篇
- 时序分析基本概念介绍<input/output delay>
- Linux 容器 vs 虚拟机 —— 谁更胜一筹
- Echarts 折线图最后一个点发光闪烁效果
- java-开发环境切换不同版本Java-jdk的步骤
- 操作系统——Windows 控制台命令
- html图片轮播幻灯片,JS+css3实现幻灯片轮播图
- GLSL 创建自己的着色器
- 两阶段最小二乘法原理_两阶段最小二乘法第一阶段为什么加入原模型外生变量...
- 佳信客服接口文档 REST API
- matlab fft能量守恒吗,功能关系 能量守恒定律
- 十月英语——坚持的力量
- 电瓶车.换电瓶(20181122)
- html css js php常用网页代码汇总合集(三)网页设计入门代码知识汇总3
- 【总结】最好的CV学习小组,超200个课时+10个方向+30个项目,从理论到实践全部系统掌握...
- 嗨格式数据恢复的 10 种最佳替代方法
- VCC 能否解决跨境支付的问题?
热门文章
- jquery 动态添加div元素(两种方式)
- Pr动态图形模板Mogrt导入失败 Mogrt is Corrupt 解决方法 Motion Graphics Templates is corrupt.
- 无人机作战效能评估系统
- python 蒙特卡罗_python实现蒙特卡罗方法教程
- 浏览器存储的方式有哪些
- malloc(): corrupted top size 解决
- 4.2 Pollard p-1算法
- ubuntu上打开md文件_Linux_查看.md
- 成长中必知的20个故事[转]
- Linux介绍及视频教程