admin管理员组文章数量:1633991
文章目录
- 一、学习背景
- 二、探索过程
- 2.1 Event事件分析
- 2.2 问题2 分析
- 2.3 正向调用
一、学习背景
最近学习网关想看看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 分析
- WebServerInitializedEvent是抽象类,其注释写的很清楚了,就是在web服务器启动之后自动发布一个event事件,而spring容器启动的时候会自动去监听事件,这个可以去看IOC部分的源码,
-
确定实现类
首先百度了解一下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; } }
-
确定实现类后,再接着Alt+F7,查看哪里调用了实现类,这里要注意,调用的位置要有publishEvent方法,因为只有此方法才能触发@EventListener处的回调,最终让我找到了,在WebServerManager有一个发布事件方法,发布了ReactiveWebServerInitializedEvent事件
void start() { this.handler.initializeHandler(); this.webServer.start(); this.applicationContext .publishEvent(new ReactiveWebServerInitializedEvent(this.webServer, this.applicationContext)); }
-
接着在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
版权声明:本文标题:pigx动态路由分析(一) 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dongtai/1729177170a1188864.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论