使用公司内部封装的 canal 数据同步工具从kafka消费数据时,@RefreshScope 注解引起的无法消费问题排查
问题代码展示:
public class CanalListenerAnnotationBeanPostProcessor implements
BeanPostProcessor, SmartInitializingSingleton, BeanFactoryPostProcessor {
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
if (notAnnotatedClasses.contains(bean.getClass())) return bean;
Class<?> targetClass = AopUtils.getTargetClass(bean);
// 只扫描类的方法,目前 CanalListener 只支持在方法上
Map<Method, CanalListener> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
(MethodIntrospector.MetadataLookup<CanalListener>) method -> findListenerAnnotations(method));
// ... 省略其它代码
annotatedMethods.entrySet().stream()
.filter(e -> e != null)
.forEach(ele -> registrars.add(new CanalListenerEndpointRegistrar(bean, ele)));
// ... 省略其它代码
return bean;
}
public void afterSingletonsInstantiated() {
// ... 省略其它代码
TransponderContainerFactory.registerListenerContainer(configurableListableBeanFactory, canalProperties, registrars, skip);
}
}
// afterSingletonsInstantiated 方法中调用该方法
private static void registerTransponderContainer(
String destination, CanalProperties.EndpointInstance endpointInstance,
ConfigurableListableBeanFactory beanFactory, List<CanalListenerEndpointRegistrar> registrarList, Skip skip) {
KafkaCanalConnector connector = CanalConnectorFactory.createConnector(destination, endpointInstance);
DmlMessageTransponderContainer singletonObject =
new DmlMessageTransponderContainer(connector, registrarList, endpointInstance, skip);
beanFactory.registerSingleton(getContainerID(destination), singletonObject);
}
public class DmlMessageTransponderContainer extends AbstractCanalTransponderContainer {}
public abstract class AbstractCanalTransponderContainer implements SmartLifecycle {
public void start() {
new Thread(() -> {
// ...
initConnect(); // 建立kafka链接,创建 KafkaConsumer
// ...
}).start();
setRunning(true);
}
}
通过上述源码我们可以看到 CanalListenerAnnotationBeanPostProcessor 实现了 BeanPostProcessor(简称 BPP) 和 SmartInitializingSingleton 这两个接口,了解 spring 的同学应该知道 spring Bean 在完成实例化后会回调 BeanPostProcessor 接口中的方法(每个对象实例化时都会回调),但是回调也是有条件限制的,后面的 spring 源码中我们会看到;但是对于 SmartInitializingSingleton 接口只会在所有单例Bean 都实例化完成后回调一次,记住 是单例Bean。
AbstractCanalTransponderContainer 对象 实现了 SmartLifecycle 生命周期接口,该接口在什么时候回调,在 spring 上下文刷新完成后进行回调(后面我们看源码)。
被 @RefreshScope 注解标注的类何时实例化呢?首先我们要知道在 spring 中有几种作用域,比如 request、session、scope等,而singleton 也属于一种特殊的对象域,在 spring 中对象的默认作用域就是 singleton 单例模式的,这类对象保存在 singletonObjects 集合中。而对于 @RefreshScope 注解标注的类 将被缓存在 scopes 中,这类对象不属于单例对象。
下面我们就来看看 spring 源码的具体实现逻辑:
spring 类扫描流程请查看 ClassPathBeanDefinitionScanner#doScan()方法,这里就不去讲解具体扫描代码了,只需要大家知道在扫描到带有 @RefreshScope 注解时会对该 BeanDefinition 做一些特殊处理,如下述内容。
**ScopedProxyUtils 类:**下述方法中我们看到 @RefreshScope 注解标注的类 在BDMap 中将会存在两个Bean定义信息:
一个名称是 原始类名,其 synthetic = true,scope=singleton,其绑定的 BD 是 proxyDefinition 代理Bean 定义信息
另一个名称是 scopedTarget.原始类名,其 synthetic = false , scope = refresh(非单例),其绑定的 BD 是原始定义信息
public abstract class ScopedProxyUtils {
private static final String TARGET_NAME_PREFIX = "scopedTarget.";
public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
BeanDefinitionRegistry registry, boolean proxyTargetClass) {
String originalBeanName = definition.getBeanName();
BeanDefinition targetDefinition = definition.getBeanDefinition();
String targetBeanName = getTargetBeanName(originalBeanName);
RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
// ...
// 将目标 Bean 定义信息注册到 BDMap 中, targetDefinition 原始 BD(Bean 定义信息) 信息
registry.registerBeanDefinition(targetBeanName, targetDefinition);
// 返回的是 代理 proxyDefinition 定义信息,也会被注册到 BDMap 中
return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
}
}
**GenericScope 类:**我们看到该类实现了 BeanFactoryPostProcessor 接口,在spring 刷新上下文的过程中 AbstractApplicationContext.refresh()中有一步是对所有实现了 BeanFactoryPostProcessor 接口的回调。在这个过程中就会回调该类,通过源码我们看到会对 @RefreshScope 注解标注的类的 BeanDefinition 信息设置 synthetic = true,该属性在Bean实例化过程中将会控制对 BPP 接口的回调。
public class GenericScope implements Scope, BeanFactoryPostProcessor, BeanDefinitionRegistryPostProcessor, DisposableBean {
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
throws BeansException {
for (String name : registry.getBeanDefinitionNames()) {
BeanDefinition definition = registry.getBeanDefinition(name);
if (definition instanceof RootBeanDefinition) {
RootBeanDefinition root = (RootBeanDefinition) definition;
if (root.getDecoratedDefinition() != null && root.hasBeanClass()
&& root.getBeanClass() == ScopedProxyFactoryBean.class) {
if (getName().equals(root.getDecoratedDefinition().getBeanDefinition().getScope())) {
// ...
root.setSynthetic(true);
}
}
}
}
}
}
对象实例化过程中 BeanPostProcessor 和 SmartInitializingSingleton 接口的回调流程
BeanPostProcessor 回调
AbstractAutowireCapableBeanFactory#initializeBean 方法,方法调用链:doCreateBean() —> initializeBean()
通过下述源码我们看到 spring 在创建bean的过程中回调了各种接口方法来对bean进行额外配置,通过这些接口我们可以对bean做额外配置
1、Aware 接口回调,通过Aware 接口我们可以获取到 bean 工厂本身
2、回调 BPP 的 postProcessBeforeInitialization 方法
3、回调 初始化方法,如 afterPropertiesSet(),用户自定义的 initMethod
4、回调 BPP 的 postProcessAfterInitialization 方法
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
// ...
invokeAwareMethods(beanName, bean);
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
// ...
invokeInitMethods(beanName, wrappedBean, mbd);
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
SmartInitializingSingleton 回调
方法调用链:AbstractApplicationContext#refresh() —> AbstractApplicationContext#finishBeanFactoryInitialization方法 —> DefaultListableBeanFactory#preInstantiateSingletons 方法
通过源码我们发现 SmartInitializingSingleton 的回调实在所有 bean 实例化之后进行的。
public void preInstantiateSingletons() throws BeansException {
// ...
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
// 实例化所有单例非懒加载的 bean
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// ...
getBean(beanName);
}
}
// 循环遍历所有单例 bean 检测是否是 SmartInitializingSingleton 实例,然后回调
for (String beanName : beanNames) {
Object singletonInstance = getSingleton(beanName);
if (singletonInstance instanceof SmartInitializingSingleton) {
final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
// ...
smartSingleton.afterSingletonsInstantiated();
}
}
}
SmartLifecycle 回调
方法调用链:AbstractApplicationContext#refresh() —> finishRefresh() 方法 —> DefaultLifecycleProcessor#onRefresh() —> startBeans()
finishRefresh() 方法在 AbstractApplicationContext#refresh() 最后调用,也即在 spring 完成所有单例bean创建后调用。在该方法中我们注意到 spring 发出了一个 上下文刷新完成的 事件 ContextRefreshedEvent 事件。
protected void finishRefresh() {
// ...
// SmartLifecycle 回调
getLifecycleProcessor().onRefresh();
// 发布上下文刷新完成事件,
publishEvent(new ContextRefreshedEvent(this));
// ...
}
private void startBeans(boolean autoStartupOnly) {
Map<String, Lifecycle> lifecycleBeans = getLifecycleBeans();
Map<Integer, LifecycleGroup> phases = new HashMap<>();
lifecycleBeans.forEach((beanName, bean) -> {
if (!autoStartupOnly || (bean instanceof SmartLifecycle && ((SmartLifecycle) bean).isAutoStartup())) {
// ...
phases.put(phase, group);
}
});
if (!phases.isEmpty()) {
// ...
for (Integer key : keys) {
phases.get(key).start(); // 调用 SmartLifecycle 的 start 方法
}
}
}
spring-cloud-context 会监听到 spring 发出的 ContextRefreshedEvent 事件,在该事件的处理过程中才会对 @RefreshScope 注解标注的类进行初始化,被 @RefreshScope 注解标注的类在生成 BeanDefiniton 的时候其 scope 的 属性将被设置为 refresh,非单例对象。
public class RefreshScope extends GenericScope implements ApplicationContextAware,
ApplicationListener<ContextRefreshedEvent>, Ordered {
public void onApplicationEvent(ContextRefreshedEvent event) {
start(event);
}
public void start(ContextRefreshedEvent event) {
if (event.getApplicationContext() == this.context && this.eager && this.registry != null) {
eagerlyInitialize();
}
}
private void eagerlyInitialize() {
for (String name : this.context.getBeanDefinitionNames()) {
BeanDefinition definition = this.registry.getBeanDefinition(name);
// 实例化 scope = "refresh" 的所有bean对象
if (this.getName().equals(definition.getScope()) && !definition.isLazyInit()) {
Object bean = this.context.getBean(name);
if (bean != null) {
bean.getClass();
}
}
}
}
}
通过上述源码分析,我们可以了解到 @RefreshScope 注解标注的类的实例化是由 spring cloud 负责实例化的,在这一步之前spring上下文已经完成了所有单例bean的实例化(完成了 bean 的生命周期接口的回调)。
回到我们的问题上来,由于 SmartInitializingSingleton 接口是单例bean实例化完成后进行回调的接口,而 @RefreshScope 注解标注的类为非单例bean,所以不回回调该接口的方法。而我们的kafka的消费者的创建过程却放在了该接口的回调中实现,所以对非单例bean不生效。
这就是该问题分析排查的整个过程,希望对大家有所帮助。