解决is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
-
自定义了一个线程池
TtlAsyncAutoConfiguration
,并且加上@EnableAsync
,希望在Application
这个应用启动类头顶就不用加这个注解也能使异步调用生效,然而却出现了5个info信息: -
@EnableAsync public class TtlAsyncAutoConfiguration implements AsyncConfigurer { @Resource private ThreadPoolTaskExecutor threadPoolTaskExecutor; @Override public Executor getAsyncExecutor() { return TtlExecutors.getTtlExecutor(threadPoolTaskExecutor); } }
-
2022-02-27 15:25:52.256 INFO 5988 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration' of type [org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2022-02-27 15:25:52.266 INFO 5988 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'spring.task.execution-org.springframework.boot.autoconfigure.task.TaskExecutionProperties' of type [org.springframework.boot.autoconfigure.task.TaskExecutionProperties] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2022-02-27 15:25:52.266 INFO 5988 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'taskExecutorBuilder' of type [org.springframework.boot.task.TaskExecutorBuilder] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2022-02-27 15:25:52.276 INFO 5988 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'applicationTaskExecutor' of type [org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) 2022-02-27 15:25:52.276 INFO 5988 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'com.github.dreamroute.ttl.config.TtlAsyncAutoConfiguration' of type [com.github.dreamroute.ttl.config.TtlAsyncAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
-
虽然是个info信息,但是由于里面提到了无法被代理,而我们知道Spring就是一个动态代理的世界,所以担心出现其他问题,于是决定解决一下这个问题。
-
警告里面有5处,挑最后一处来解决。
-
根据告警信息找到打印信息的类
BeanPostProcessorChecker
,方法是: -
@Override public Object postProcessAfterInitialization(Object bean, String beanName) { if (!(bean instanceof BeanPostProcessor) && !isInfrastructureBean(beanName) && this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount) { if (logger.isInfoEnabled()) { logger.info("Bean '" + beanName + "' of type [" + bean.getClass().getName() + "] is not eligible for getting processed by all BeanPostProcessors " + "(for example: not eligible for auto-proxying)"); } } return bean; }
-
根据
if
条件看出,打印这个警告的原因有3个:!(bean instanceof BeanPostProcessor)
,这个bean不是BeanPostProcessor
类型的!isInfrastructureBean(beanName)
,这个bean不是Spring
基础bean,Spring
在BeanDefinition
中定义了3种类型的bean,我们可以通过注解@Role
或者在类似BeanFactoryPostProcessor
这种钩子种修改beandefenition的类型为2this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount)
,所有BeanPostProcessor
类型的bean还没有完全被注册,就注册了TtlThreadPoolAutoConfiguration
这个bean
-
前两个原因好理解,第三条原因解释:在
Spring
中,如果一个bean实现了BeanPostProcessor
接口,那么是要优先于普通bean的注册的,也就是说所有的BeanPostProcessor
类型的bean注册完成之后才注册普通bean,那么如果某个普通bean早于某个BeanPostProcessor
bean注册,就会被上面的检测方法检测到并且提示告警信息 -
所以解决这个告警信息的手段也就是有3个:
- 让此bean实现
BeanPostProcessor
接口 - 使得此bean为
Spring
的基础bean - 后于所有
BeanPostProcessor
注册
- 让此bean实现
-
前面2个方法警告是可以消除,但是对象不能被代理,但可能存在隐藏问题,所以继续
-
查了下
Spring
官网(地址:https://www.baeldung.com/spring-not-eligible-for-auto-proxying)对这个警告的解释,bean注册的时机不对,过早了。官方解释:因为是在BeanPostProcessor
中注册了某个bean,而aop代理也是一个BeanPostProcessor
,所以说BeanPostProcessor
这种bean本身,以及这种bean内部依赖的bean都不会被代理。 -
例子中如果未使用
@Lazy
那么虽然能够被正确注入,但是并不会产生随机数,那么为什么会出现这种情况呢?既然是个正常的Spring
bean,并且能够正常注入,但是无法产生随机数呢?在RandomIntProcessor
的postProcessBeforeInitialization
方法中打上断点,发现如果不是@Lazy
方式,dataCache
这个类压根儿不会经过此方法,而如果使用@Lazy
就会经过此方法,跟踪调用链发现原因所在:当所有BeanPostProcessor
类型的bean都注册完成之后再注册普通bean,并且普通bean在注册完毕之后会被所有的BeanPostProcessor
bean都处理一次,在类AbstractAutowireCapableBeanFactory
的方法中: -
@Override public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessBeforeInitialization(result, beanName); if (current == null) { return result; } result = current; } return result; }
-
所以官网例子中的
dataCache
过早被注册到IOC容器,他就不会从上面方法中被处理。 -
所以说:要彻底解决这个问题,首先将属性加上
@Lazy
注解,用于确保不会被提前初始化,我们需要判断一下我们定义的bean是否需要被spring中的BeanPostProcessor
类型的所有bean(自动装配、安全性或事务性注释)洗礼一次,如果确实不需要,那么使用解决方案1、2就可以,如果需要或者无法判断需不需要,那么就需要使用第三种解决方案。 -
我们这里的将
-
@Resource private ThreadPoolTaskExecutor threadPoolTaskExecutor;
-
改为
-
@Lazy @Resource private ThreadPoolTaskExecutor threadPoolTaskExecutor;
-
再次启动:发现原来有5个警告,还剩下一处:
-
Bean 'com.github.dreamroute.ttl.config.TtlAsyncAutoConfiguration' of type [com.github.dreamroute.ttl.config.TtlAsyncAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
-
也就是说,其他延迟注册已经起作用了,这里
TtlAsyncAutoConfiguration
注册还是过早了。在警告打印出打上断点,继续跟踪堆栈信息,发现是在注册一个叫做org.springframework.context.annotation.internalAsyncAnnotationProcessor
的BeanPostProcessor
时候提前初始化了TtlAsyncAutoConfiguration
类。从BeanDefinitionMap
中发现是internalAsyncAnnotationProcessor
来源于ProxyAsyncConfiguration
这个类: -
@Configuration(proxyBeanMethods = false) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration { @Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public AsyncAnnotationBeanPostProcessor asyncAdvisor() { Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected"); AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor(); bpp.configure(this.executor, this.exceptionHandler); Class<? extends Annotation> customAsyncAnnotation = this.enableAsync.getClass("annotation"); if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) { bpp.setAsyncAnnotationType(customAsyncAnnotation); } bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass")); bpp.setOrder(this.enableAsync.<Integer>getNumber("order")); return bpp; } }
-
那么也就是说,在注册上面这个类的时候初始化了
TtlAsyncAutoConfiguration
。 -
ProxyAsyncConfiguration
类会被注册的原因在于,我们的TtlAsyncAutoConfiguration
头顶存在@Aysnc
注解: -
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(AsyncConfigurationSelector.class) public @interface EnableAsync {
-
注解上的
@Import(AsyncConfigurationSelector.class)
的属性AsyncConfigurationSelector.class
在 -
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> { private static final String ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME = "org.springframework.scheduling.aspectj.AspectJAsyncConfiguration"; /** * Returns {@link ProxyAsyncConfiguration} or {@code AspectJAsyncConfiguration} * for {@code PROXY} and {@code ASPECTJ} values of {@link EnableAsync#mode()}, * respectively. */ @Override @Nullable public String[] selectImports(AdviceMode adviceMode) { switch (adviceMode) { case PROXY: return new String[] {ProxyAsyncConfiguration.class.getName()}; case ASPECTJ: return new String[] {ASYNC_EXECUTION_ASPECT_CONFIGURATION_CLASS_NAME}; default: return null; } } }
-
方法
selectImports
会用到@EnableAsync
注解,所以会提前注册TtlAsyncAutoConfiguration
类 -
并且:
ProxyAsyncConfiguration
内的bean是一个BeanProcessor
类型的bean -
@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public AsyncAnnotationBeanPostProcessor asyncAdvisor() { Assert.notNull(this.enableAsync, "@EnableAsync annotation metadata was not injected"); AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor(); bpp.configure(this.executor, this.exceptionHandler); Class<? extends Annotation> customAsyncAnnotation = this.enableAsync.getClass("annotation"); if (customAsyncAnnotation != AnnotationUtils.getDefaultValue(EnableAsync.class, "annotation")) { bpp.setAsyncAnnotationType(customAsyncAnnotation); } bpp.setProxyTargetClass(this.enableAsync.getBoolean("proxyTargetClass")); bpp.setOrder(this.enableAsync.<Integer>getNumber("order")); return bpp; }
-
所以:初始化
AsyncAnnotationBeanPostProcessor
这个BeanProcessor
导致了提前初始化TtlAsyncAutoConfiguration
。 -
结论:因为存在
@EnableAsync
造成了提前初始化TtlAsyncAutoConfiguration
。并且@Lazy
不会生效,因为确实是在这个AsyncAnnotationBeanPostProcessor
里面需要用到TtlAsyncAutoConfiguration
,没办法延迟加载。 -
解决:将
TtlAsyncAutoConfiguration
头顶的@EnableAsync
注解移除,挪到应用启动类头顶,即可消除所有警告,两个类如下: -
public class TtlAsyncAutoConfiguration implements AsyncConfigurer { @Lazy @Resource private ThreadPoolTaskExecutor threadPoolTaskExecutor; @Override public Executor getAsyncExecutor() { return TtlExecutors.getTtlExecutor(threadPoolTaskExecutor); } }
-
@EnableAsync @SpringBootApplication @EnableTransactionManagement public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
-
这样就彻底解决问题了。由于不知道
TtlAsyncAutoConfiguration
如果不经过所有的BeanProcessor
的洗礼会不会有问题,所以,就把@EnableAsync挪开吧。只是需要注意的是今后需要把@EnableAsync
配置在启动类的头顶 -
由此引发的思考:事实上,这种问题的出现,或许不仅仅是我这里存在,如果其他地方也存在,而spring启动日志又比较多,这种info日志容易漏看,因此编写一个检测工具,启动应用时候如果发现有打印这个日志就打印一个异常日志
-
原理:定义一个logback的
Appender
,启动引用时,启动监控控制台日志,容器refresh完毕,检查日志是否存在is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
就打印一个异常日志,并且将此Appender
禁用掉,因为打印这个日志是在refresh
方法的registerBeanPostProcessors(beanFactory)
中打印的,所以如果有警告信息,铁定已经打印出来了,因此就没必要留着自定义Appender继续打印日志了。 -
由此,编写一个springboot的starter组件,地址:https://github.com/Dreamroute/bbp-monitor
-
此组件已经上传到mvn中央库,可以直接使用
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
2018-02-28 ElasticSearch原理