这篇介绍了如何从接收事件的方法逆向推出完整的事件处理过程,这个方法适合在解决具体问题或学习源码时,倒着把处理过程理顺。

起因

原来用的 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 在什么时候添加到 TreeCachelisteners 中的?

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,一层层往上找,直到下面的位置:

  1. 可以看到这里创建 new InstanceRegisteredEvent
  2. 当前执行代码在抽象类 AbstractAutoServiceRegistrationstart 方法
  3. 当前的实现类为 ZookeeperAutoServiceRegistration

此处还有另一个【简单分支】,后文分析。

继续看 AbstractAutoServiceRegistration,发现是从当前类中按照下面的方法顺序调用的:

  1. void onApplicationEvent(WebServerInitializedEvent event)
  2. void bind(WebServerInitializedEvent event)
  3. 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> {

前面说到通过 AbstractAutoServiceRegistrationstart 方法将自己添加到了监听列表。这个类本身是通过下面的方式注册的:

当没有实例 "org.springframework.cloud.zookeeper.discovery.ZookeeperLifecycle",没有配置 "spring.cloud.service-registry.auto-registration.enabled" 或者配置为 true 时,这个类都会生效。

总结

通过本文逆向分析的过程解决了遇到的问题,同时也了解了事件的部分处理过程。本文分析的这个过程是最重要的,由于 Spring 框架中的 IOC、AOP以及各种为了扩展性使用的设计模式、解耦和异步调用的逻辑,直接通过源码看调用时不知道具体的实现是哪一个, 但是逆向分析时,你看到的都是具体的某一个实例的执行。 通过具体实例的执行,看到的也许只是正向过程的一个分支。通过逆向得到的执行路径,反着来看就是一个正向的路径,知道一个正向路径后就可以在正向分析时,再探索其他的分支路径。通过逆向和正向的反复结合,最终弄清理解代码,解决问题,实现扩展等方面的需求。

Dubbo Spring Cloud 逆向分析服务注册事件变化的处理过程相关推荐

  1. Spring Cloud之acos服务注册与Dubbo

    Spring Cloud之acos服务注册与Dubbo nacos是springcloud的扩展,注册中心功能通过NacosDiscoveryClient 继承DiscoveryClient,在spr ...

  2. Spring Cloud入门 -- Consul服务注册与发现(Hoxton.SR5版)

    Consul简介 Consul 是 HashiCorp 公司推出的开源产品,用于实现分布式系统的服务发现.服务隔离.服务配置,这些功能中的每一个都可以根据需要单独使用,也可以同时使用所有功能.Cons ...

  3. [Spring-cloud-eureka]使用 gradle, spring boot,Spring cloud Eureka 搭建服务注册中心

    2019独角兽企业重金招聘Python工程师标准>>> Spring Cloud Eureka 是 Spring Cloud Netflix 微服务套件的一部分,它基于 Netfli ...

  4. spring cloud gateway之服务注册与发现

    点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 在之前的文章介绍了Spring Cloud Gateway的Predict(断言).Filter( ...

  5. Spring Cloud入门 -- Eureka服务注册与发现(Hoxton.SR5版)

    什么是Spring Cloud Spring Cloud是一系列框架的有序集合.它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册.配置中心.智能路由.消息总 ...

  6. spring cloud 学习之 服务注册和发现(Eureka)

    一:服务注册和发现(Eureka) 1:采用Eureka作为服务注册和发现组件 2:Eureka 项目中 主要在启动类加上 注解@EnableEurekaServer @SpringBootAppli ...

  7. 【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 ...

  8. 【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 ...

  9. Spring Cloud与微服务学习总结(2)——Spring Cloud相较于Dubbo等RPC服务框架的优势

    摘要: 目前,Spring Cloud在国内的知名度并不高,在前阵子的求职过程中,与一些互联网公司的架构师.技术VP或者CTO在交流时,有些甚至还不知道该项目的存在.可能这也与国内阿里巴巴开源服务治理 ...

最新文章

  1. 【Harvest源码分析】GetFilteredSignal函数
  2. PHP下实现两种ajax跨域的解决方案之jsonp
  3. 【渝粤题库】陕西师范大学163202 管理学原理 作业(高起本 专升本)
  4. python学习精华——成长篇
  5. 时序分析基本概念介绍<input/output delay>
  6. Linux 容器 vs 虚拟机 —— 谁更胜一筹
  7. Echarts 折线图最后一个点发光闪烁效果
  8. java-开发环境切换不同版本Java-jdk的步骤
  9. 操作系统——Windows 控制台命令
  10. html图片轮播幻灯片,JS+css3实现幻灯片轮播图
  11. GLSL 创建自己的着色器
  12. 两阶段最小二乘法原理_两阶段最小二乘法第一阶段为什么加入原模型外生变量...
  13. 佳信客服接口文档 REST API
  14. matlab fft能量守恒吗,功能关系 能量守恒定律
  15. 十月英语——坚持的力量
  16. 电瓶车.换电瓶(20181122)
  17. html css js php常用网页代码汇总合集(三)网页设计入门代码知识汇总3
  18. 【总结】最好的CV学习小组,超200个课时+10个方向+30个项目,从理论到实践全部系统掌握...
  19. 嗨格式数据恢复的 10 种最佳替代方法
  20. VCC 能否解决跨境支付的问题?

热门文章

  1. jquery 动态添加div元素(两种方式)
  2. Pr动态图形模板Mogrt导入失败 Mogrt is Corrupt 解决方法 Motion Graphics Templates is corrupt.
  3. 无人机作战效能评估系统
  4. python 蒙特卡罗_python实现蒙特卡罗方法教程
  5. 浏览器存储的方式有哪些
  6. malloc(): corrupted top size 解决
  7. 4.2 Pollard p-1算法
  8. ubuntu上打开md文件_Linux_查看.md
  9. 成长中必知的20个故事[转]
  10. Linux介绍及视频教程