-
自定義了一個線程池
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中央庫,可以直接使用