解決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個:

    1. !(bean instanceof BeanPostProcessor),這個bean不是BeanPostProcessor類型的
    2. !isInfrastructureBean(beanName),這個bean不是Spring基礎bean,SpringBeanDefinition中定義了3種類型的bean,我們可以通過注解@Role或者在類似BeanFactoryPostProcessor這種鈎子種修改beandefenition的類型為2
    3. this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount),所有BeanPostProcessor類型的bean還沒有完全被注冊,就注冊了TtlThreadPoolAutoConfiguration這個bean
  • 前兩個原因好理解,第三條原因解釋:在Spring中,如果一個bean實現了BeanPostProcessor接口,那么是要優先於普通bean的注冊的,也就是說所有的BeanPostProcessor類型的bean注冊完成之后才注冊普通bean,那么如果某個普通bean早於某個BeanPostProcessorbean注冊,就會被上面的檢測方法檢測到並且提示告警信息

  • 所以解決這個告警信息的手段也就是有3個:

    1. 讓此bean實現BeanPostProcessor接口
    2. 使得此bean為Spring的基礎bean
    3. 后於所有BeanPostProcessor注冊
  • 前面2個方法警告是可以消除,但是對象不能被代理,但可能存在隱藏問題,所以繼續

  • 查了下Spring官網(地址:https://www.baeldung.com/spring-not-eligible-for-auto-proxying)對這個警告的解釋,bean注冊的時機不對,過早了。官方解釋:因為是在BeanPostProcessor中注冊了某個bean,而aop代理也是一個BeanPostProcessor,所以說BeanPostProcessor這種bean本身,以及這種bean內部依賴的bean都不會被代理。

  • 例子中如果未使用@Lazy那么雖然能夠被正確注入,但是並不會產生隨機數,那么為什么會出現這種情況呢?既然是個正常的Springbean,並且能夠正常注入,但是無法產生隨機數呢?在RandomIntProcessorpostProcessBeforeInitialization方法中打上斷點,發現如果不是@Lazy方式,dataCache這個類壓根兒不會經過此方法,而如果使用@Lazy就會經過此方法,跟蹤調用鏈發現原因所在:當所有BeanPostProcessor類型的bean都注冊完成之后再注冊普通bean,並且普通bean在注冊完畢之后會被所有的BeanPostProcessorbean都處理一次,在類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.internalAsyncAnnotationProcessorBeanPostProcessor時候提前初始化了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中央庫,可以直接使用


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM