解决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中央库,可以直接使用

posted @   神一样的存在  阅读(5683)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
历史上的今天:
2018-02-28 ElasticSearch原理
点击右上角即可分享
微信分享提示