引入链路追踪组件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;
	}
}
相关推荐
.生产的驴几秒前
SpringCloud OpenFeign用户转发在请求头中添加用户信息 微服务内部调用
spring boot·后端·spring·spring cloud·微服务·架构
xoxo-Rachel7 分钟前
(超级详细!!!)解决“com.mysql.jdbc.Driver is deprecated”警告:详解与优化
java·数据库·mysql
乌啼霜满天2498 分钟前
JDBC编程---Java
java·开发语言·sql
微信-since8119216 分钟前
[ruby on rails] 安装docker
后端·docker·ruby on rails
色空大师21 分钟前
23种设计模式
java·开发语言·设计模式
闲人一枚(学习中)22 分钟前
设计模式-创建型-建造者模式
java·设计模式·建造者模式
2202_7544215439 分钟前
生成MPSOC以及ZYNQ的启动文件BOOT.BIN的小软件
java·linux·开发语言
蓝染-惣右介42 分钟前
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
java·数据库·tomcat·mybatis
小林想被监督学习43 分钟前
idea怎么打开两个窗口,运行两个项目
java·ide·intellij-idea