引入链路追踪组件sleuth后的一个踩坑

最近在公司项目中引入链路追踪组件sleuth后,启动项目报BeanNotOfRequiredTypeException的异常,看报错信息,是说关于线程池bean ThreadPoolExecutor和TraceableExecutorService的类型冲突问题,项目中定义了一个ThreadPoolExecutor的bean,如下:

scss 复制代码
@Bean
public ThreadPoolExecutor threadPoolExecutor(ThreadPoolExecutorProperties properties) {	
	return new ThreadPoolExecutor(properties.getCorePoolSize(),
			properties.getMaximumPoolSize(),
			properties.getKeepAliveTime(),
			properties.getTimeUnit(),
			new ArrayBlockingQueue<>(properties.getCapacity()),
			new ThreadPoolExecutor.AbortPolicy());
}

在项目其他地方注入了这个线程池bean,按合情合理,不应该报错的, 决定跟踪下源码看到底是什么原因会导致。

首先跟踪threadPoolExecutor的bean创建,在bean创建后的初始化initializeBean部分:

less 复制代码
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
	//........省略部分代码

	Object wrappedBean = bean;
	//调用前置处理器
	wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);

	//初始化
	invokeInitMethods(beanName, wrappedBean, mbd);
	
	//调用后置处理器
	wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

	return wrappedBean;
}

在后置处理器中有一个ExecutorBeanPostProcessor处理器,跟踪进去:

typescript 复制代码
public class ExecutorBeanPostProcessor implements BeanPostProcessor {

	//.........省略部分代码

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if (!ExecutorInstrumentor.isApplicableForInstrumentation(bean)) {
			return bean;
		}
		//调用ExecutorInstrumentor.instrument方法
		return new ExecutorInstrumentor(() -> sleuthAsyncProperties().getIgnoredBeans(), this.beanFactory)
				.instrument(bean, beanName);
	}
}

public Object ExecutorInstrumentor.instrument(Object bean, String beanName) {
	//.......省略部分代码
	if (bean instanceof ExecutorService) {
		if (isProxyNeeded(beanName)) {
			return wrapExecutorService(bean, beanName);
		}		
	}
	//.......省略部分代码
	return bean;
}

boolean isProxyNeeded(String beanName) {
	//判断忽略的bean是否包含当前创建的bean
	return !this.ignoredBeans.get().contains(beanName);
}

private Object wrapExecutorService(Object bean, String beanName) {
	ExecutorService executor = (ExecutorService) bean;
	boolean classFinal = Modifier.isFinal(bean.getClass().getModifiers());
	boolean methodFinal = anyFinalMethods(executor);
	boolean cglibProxy = !classFinal && !methodFinal;
	//封装线程池bean
	return createExecutorServiceProxy(bean, cglibProxy, executor, beanName);
}

Object createExecutorServiceProxy(Object bean, boolean cglibProxy, ExecutorService executor, String beanName) {
	return getProxiedObject(bean, beanName, cglibProxy, executor, () -> {
		if (executor instanceof ScheduledExecutorService) {
			return TraceableScheduledExecutorService.wrap(this.beanFactory, executor, beanName);
		}
		//封装成TraceableExecutorService
		return TraceableExecutorService.wrap(this.beanFactory, executor, beanName);
	});
}

可以看到在后置处理器中针对ExecutorService的子类单独做了处理,如果当前创建的bean在忽略的bean里面,则不做任何封装直接返回,否则封装成TraceableExecutorService对象。也就是说我们保存在Spring容器里面的线程池对象被封装成TraceableExecutorService对象存储了。

最后在Spring的中会判断类型是否匹配,如果不能匹配就抛出异常BeanNotOfRequiredTypeException。

typescript 复制代码
if (!ClassUtils.isAssignableValue(type, result)) {
	throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
}

至此报错原因是找到了,解决办法也很简单,就是把这个线程池的bean加入到忽略bean中,配置如下:

yaml 复制代码
spring:
  sleuth:
    async:
      ignoredBeans:
        - threadPoolExecutor

配置是通过配置文件SleuthAsyncProperties加载的:

typescript 复制代码
@ConfigurationProperties(prefix = "spring.sleuth.async")
public class SleuthAsyncProperties {
	private boolean enabled;

	public boolean isEnabled() {
		return this.enabled;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
	}
	
	private List<String> ignoredBeans = Collections.emptyList();

	public List<String> getIgnoredBeans() {
		return this.ignoredBeans;
	}

	public void setIgnoredBeans(List<String> ignoredBeans) {
		this.ignoredBeans = ignoredBeans;
	}
}
相关推荐
字节跳动数据库9 分钟前
文章分享——相似函数处理方法
人工智能·后端·程序员
云技纵横9 分钟前
@Transactional 失效的 7 种场景:第 5 种最难排查
后端
用户67570498850226 分钟前
你知道 Go 结构体和结构体指针调用的区别吗?一文带你彻底搞懂!
后端·go
程序员cxuan42 分钟前
读懂 Claude Code 架构分析系列,第一篇,开始!
人工智能·后端·架构
用户6757049885021 小时前
面试官问“装饰器模式”,这样回答薪资多要 3000!
后端
tntxia1 小时前
Geo Scene域名修改引起的一些问题
后端
用户298698530141 小时前
Java 实现 Word 文档加密与权限解除
java·后端
vanuan1 小时前
给你的A2A-Agent加把锁-认证鉴权实战指南
后端
Yeats_Liao1 小时前
14:Servlet中的页面跳转-Java Web
java·后端·架构