pigx动态路由分析(一)

一、学习背景

最近学习网关想看看Pigx项目中的实现方式,发现pigx的作者采用的动态加载路由的方式,路由配置是从数据库读取,而非直接配置route。并且它的动态路由加载没有放到apigateway模块,是放到用户权限管理模块(upms-biz)。通过搜索upms服务启动日志关键字定位到了DynamicRouteInitRunner这个类,此类会初始化网管路由,但是不明白是何时触发的路由初始化

	@Async
	@Order
	@EventListener({ WebServerInitializedEvent.class, DynamicRouteInitEvent.class })
	public void initRoute() {
		log.info("当前线程:"+Thread.currentThread().getName());
		Boolean result = redisTemplate.delete(CacheConstants.ROUTE_KEY);
		log.info("初始化网关路由 {} ", result);

		routeConfService.list().forEach(route -> {
			RouteDefinitionVo vo = new RouteDefinitionVo();
			vo.setRouteName(route.getRouteName());
			vo.setId(route.getRouteId());
			vo.setUri(URI.create(route.getUri()));
			vo.setOrder(route.getOrder());

			JSONArray filterObj = JSONUtil.parseArray(route.getFilters());
			vo.setFilters(filterObj.toList(FilterDefinition.class));
			JSONArray predicateObj = JSONUtil.parseArray(route.getPredicates());
			vo.setPredicates(predicateObj.toList(PredicateDefinition.class));

			log.info("加载路由ID:{},{}", route.getRouteId(), vo);
			redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinitionVo.class));
			redisTemplate.opsForHash().put(CacheConstants.ROUTE_KEY, route.getRouteId(), vo);
		});
		log.debug("初始化网关路由结束 ");
	}

二、探索过程

2.1 Event事件分析

大方向上采取反向分析的方式,通过Idea的Alt+F7一步一步查找,不直接采用正向分析的方式查看spring容器启动的源码,因为真的太多太多了,打断点都不知道在哪里打。

  • 问题1:通过@EventListener注解可以知道,这是通过事件来触发的,但是里面有两个触发类,究竟是哪个类触发的呢?

要想确认是哪个触发的,只需要在方法中打上断点,先删除一个,然后debug启动,看看会不会进入断点。通过此方式可以确定是WebServerInitializedEvent事件出发了initRoute回调。

  • 问题2:何时触发的WebServerInitializedEvent呢?
2.2 问题2 分析
  1. WebServerInitializedEvent是抽象类,其注释写的很清楚了,就是在web服务器启动之后自动发布一个event事件,而spring容器启动的时候会自动去监听事件,这个可以去看IOC部分的源码,

在这里插入图片描述

  1. 确定实现类

    首先百度了解一下WebServerInitializedEvent,这个类的作用大致是spring容器启动后做一些进本信息获取工作,比如获取服务端口是最常见的。在此类上Alt+F7,查找疑似的调用,然后打上断点,可以在疑似位置都打上断点,防止打错断点,debug的时候就错过了,每打一个断点,重启一下,确保断点能正常进入。我在查找时发下一下有个onApplicationEvent调用了WebServerInitializedEvent,然后打断点,这里就可以确认WebServerInitializedEvent的实际实现是ReactiveWebServerInitializedEvent

    @Component
    public class ApplicationStartListener implements ApplicationListener<WebServerInitializedEvent> {
    
       @Override
       public void onApplicationEvent(WebServerInitializedEvent event) {
          int serverPort = event.getWebServer().getPort();
          String ip = getIp();
          Constants.address = ip + ":" + serverPort;
       }
    
       private String getIp() {
          String host = null;
          try {
             host = InetAddress.getLocalHost().getHostAddress();
          }
          catch (UnknownHostException e) {
             e.printStackTrace();
          }
          return host;
       }
    
    }
    
    1. 确定实现类后,再接着Alt+F7,查看哪里调用了实现类,这里要注意,调用的位置要有publishEvent方法,因为只有此方法才能触发@EventListener处的回调,最终让我找到了,在WebServerManager有一个发布事件方法,发布了ReactiveWebServerInitializedEvent事件

      void start() {
         this.handler.initializeHandler();
         this.webServer.start();
         this.applicationContext
               .publishEvent(new ReactiveWebServerInitializedEvent(this.webServer, this.applicationContext));
      }
      
    2. 接着在start方法上继续Alt+F7,一直找它的上级调用方法,打上断点,启动服务,debug,然后就得到了调用链。

2.3 正向调用

通过以上分析,我们逆推出来了调用链,回过头再看看正向的调用过程。这里提一下,除了Alt+F7的方式逆推调用链,还可以在Idea中debug的时候通过drop frame的方式,如下图所示,当断点运行到refreshContext时,我们点击Drop Frame会跳转到调用refreshContext的函数中。看过java虚拟机相关书籍的同学应该比较熟悉frame(栈帧)的概念,就是丢掉当前帧,回到上一个位置,那当然就回到了调用它的位置。

在这里插入图片描述

至于上面我们为何没采用这种方式逆推调用链,是因为事件回调这种使用了代理,drop frame会回到xxxx.invoke()之类的方法中,不是真正的上级调用方法。

在这里插入图片描述

在这里插入图片描述

逆推到后面,会看到进入了AbstractApplicationContext的refresh方法,这个方法很经典,看过spring容器初始化源码的一定很熟悉这个方法,因为这个方法是容器初始化的入口,不管是注解启动(springboot),还是springMVC基本web项目,还是手动new ClasspathXMLApplicationContext(),最终都会走到这个refresh方法

@Override
public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // Prepare the bean factory for use in this context.
      prepareBeanFactory(beanFactory);

      try {
         // Allows post-processing of the bean factory in context subclasses.
         postProcessBeanFactory(beanFactory);

         // Invoke factory processors registered as beans in the context.
         invokeBeanFactoryPostProcessors(beanFactory);

         // Register bean processors that intercept bean creation.
         registerBeanPostProcessors(beanFactory);

         // Initialize message source for this context.
         initMessageSource();

         // Initialize event multicaster for this context.
         initApplicationEventMulticaster();

         // Initialize other special beans in specific context subclasses.
         onRefresh();

         // Check for listener beans and register them.
         registerListeners();

         // Instantiate all remaining (non-lazy-init) singletons.
         finishBeanFactoryInitialization(beanFactory);

         // Last step: publish corresponding event.
         finishRefresh();
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

         // Destroy already created singletons to avoid dangling resources.
         destroyBeans();

         // Reset 'active' flag.
         cancelRefresh(ex);

         // Propagate exception to caller.
         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}

finishRefresh();方法上有注释,翻译过来就是发布事件,在这个位置打断点跟踪下去,getLifecycleProcessor().onRefresh();上打断点继续跟踪

protected void finishRefresh() {
   // Clear context-level resource caches (such as ASM metadata from scanning).
   clearResourceCaches();

   // Initialize lifecycle processor for this context.
   initLifecycleProcessor();

   // Propagate refresh to lifecycle processor first.
   getLifecycleProcessor().onRefresh();

   // Publish the final event.
   publishEvent(new ContextRefreshedEvent(this));

   // Participate in LiveBeansView MBean, if active.
   LiveBeansView.registerApplicationContext(this);
}
@Override
public void onRefresh() {
   startBeans(true);
   this.running = true;
}

后面太多了,不想跟踪了,套路都一样。

end

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值