【小家Spring】注意BeanPostProcessor启动时对依赖Bean的“误伤”陷阱(is not eligible for getting processed by all...)

每篇一句

祝你们五一国际劳动节快乐~你是在快乐的撸码,仍是在快乐的浪呢?java

相关阅读

【小家Spring】Spring IOC容器启动流程 AbstractApplicationContext#refresh()方法源码分析(一)
【小家Spring】Spring IOC容器启动流程 AbstractApplicationContext#refresh()方法源码分析(二),Spring容器启动/刷新的完整总结
【小家Spring】为脱离Spring IOC容器管理的Bean赋能【依赖注入】的能力,并分析原理(借助AutowireCapableBeanFactory赋能)
【小家Spring】细说Spring IOC容器的自动装配(@Autowired),以及Spring4.0新特性之【泛型依赖注入】的源码级解析web

前言

本篇博文和Spring的上下文启动有较强的关联性,同时须要读者对Spring中的BeanPostProcessor有较为熟悉的了解。若以前没有接触过的同窗,建议先点击一下相关阅读的文章列表,先对Spring容器有个大体的了解会效果更佳~spring

这是曾发生在我原公司工做中的一个Spring项目的真实场景案例:简单的描述就是在使用Spring整合@Async、security的时候,出现一个诡异的现象:我把security整合进后原来的@Async就木有生效了,可是若是不把security集成进来的话,就能正常work缓存

当时还觉得是spring-security的问题,甚至觉得是它的bug,如今想起来确实是本身当初图样图森破,切忌不要轻易下结论啊~微信

其实当初我也没找到根本缘由,而是经过另一种集成方式绕过了就继续撸码了。可是我内心一直记着此事,由于我认为一个问题你不知道它根本缘由的时候,它就像个定时炸弹,随时可能被引爆。介于此机会,因此此处拿出来跟你们分享分享,避免采坑哈~app

记忆中惟一线索:BeanPostProcessorChecker这个后置处理器输出了一句 xxx is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)这样的日志~~~~今天忽然想起,其实这就是个很大的突破口(由于这句日志通常状况下是不会输出的~)框架

本文就不还原当时的场景了,而是以一个模拟的场景进行讲解、定位问题最后解决问题异步

什么是BeanPostProcessor

BeanPostProcessor是Spring的Bean工厂中一个很是重要的钩子,容许Spring框架在新建立Bean实例时对其进行定制化修改。好比咱们对Bean内容进行修改、建立代理对象等等~async

BeanPostProcessor自己也是一个Bean,通常而言其实例化时机要早过普通的Bean,可是BeanPostProcessor有时也会依赖一些Bean,这就致使了一些普通Bean的实例化早于BeanPostProcessor的可能状况,由此若是使用不当,就会形成一些问题ide

场景模拟

如今经过我本身构造的一个场景,来模拟当时出现的问题~
先看看只使用@Aysnc的现象:

@EnableAsync
@Configuration
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 这一步千万不能忘了,不然报错: java.lang.IllegalStateException: ThreadPoolTaskExecutor not initialized
        // 并且最好放在最上面 不然下面set方法对Executor都不会生效
        executor.initialize();

        executor.setCorePoolSize(10); //核心线程数
        executor.setMaxPoolSize(20);  //最大线程数
        executor.setQueueCapacity(1000); //队列大小
        executor.setKeepAliveSeconds(300); //线程最大空闲时间
        executor.setThreadNamePrefix("fsx-Executor-"); ////指定用于新建立的线程名称的前缀。
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 拒绝策略(一共四种,此处省略)
        return executor;
    }

    // 异常处理器:固然你也能够自定义的,这里我就这么简单写了~~~
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new SimpleAsyncUncaughtExceptionHandler();
    }
}

使用@Async

@Service
public class HelloServiceImpl implements HelloService {

	@Async
    @Override
    public Object hello() {
        log.info("当前线程:" + Thread.currentThread().getName());
        return "service hello";
    }
}

启动、测试:

16:02:58.520 [fsx-Executor-3] INFO  com.fsx.service.HelloServiceImpl - 当前线程:fsx-Executor-3

能够看到使用的是咱们自定义的线程池里面的线程,而且HelloService是个Proxy代理对象了。@Async可以正常work,没毛病老铁


接下来加入我再加入一个组件:MyBeanPostProcessor

@Slf4j
@Component
public class MyBeanPostProcessor implements BeanPostProcessor, Ordered {

    @Autowired
    private ApplicationContext applicationContext;

	// 目的:在此BeanPostProcessor初始化的时候,提早把HelloServiceImpl给初始化掉~
	// 经过这种方式来模拟:咱们的BeanProcessor须要依赖业务的service、dao等状况~
    @PostConstruct
    public void init() {
        HelloService helloService = applicationContext.getBean(HelloService.class);
        System.out.println(helloService.getClass());
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}

或者这么写,直接使用@Autowired注入属性~~~

@Slf4j
@Component
public class MyBeanPostProcessor implements BeanPostProcessor, Ordered {

    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    private HelloService helloService;

    @PostConstruct
    public void init() {
        System.out.println(helloService.getClass());
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}

最终启动测试,输出为:

启动输出:
...
o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'helloServiceImpl' of type [com.fsx.service.HelloServiceImpl] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
class com.fsx.service.HelloServiceImpl
...

发现启动的时候,输出了BeanPostProcessorChecker这个Bean检查的处理器的日志
并且,并且咱们的helloService这个Bean再也不是Proxy代理对象了~

再次请求执行目标方法看看:

14:34:50.164 [http-nio-8080-exec-3] INFO  com.fsx.service.HelloServiceImpl - 当前线程:http-nio-8080-exec-3

应该能猜到了,它已经不是在咱们的异步线程池里面执行了,很显然@Aysnc此时就没有再生效了

致使这个现象的缘由:就是咱们在开发过程当中,由于不清楚Spring容器对BeanPostProcessor、Bean的装载顺序,从而致使有时候咱们须要提早用到Bean的功能,从而致使启动时的"误伤"

关于BeanPostProcessor的加载顺序

可能有的人会有疑问,为何你这里(MyBeanPostProcessor)可以直接@Autowired,可是我这里为何获得的是Null呢?
其实这里面是有文章可寻的,那就是BeanPostProcessor的加载顺序:

【小家Spring】Spring IOC容器启动流程 AbstractApplicationContext#refresh()方法源码分析(一)
【小家Spring】Spring IOC容器启动流程 AbstractApplicationContext#refresh()方法源码分析(二),Spring容器启动/刷新的完整总结

Spring容器启动过程,从向容器注册BeanPostProcessor这一步开始说明:

registerBeanPostProcessors(beanFactory);
public static void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
	
		// 注意:此处只会拿到Bean的定义信息~~~~
		// 已经被实例化的Bean最终都会调用`beanFactory.addBeanPostProcessor`而缓存在AbstractBeanFactory的字段:beanPostProcessors里,它是个CopyOnWriteArrayList
		// 更重要的是:最终最终全部的BeanPostProcessor的执行都会从这个List里面拿出来执行
		// 因此这一步很关键:那就是按照顺序,把`BeanPostProcessor`们都实例化好,而后添加进List里
		// 所以顺序是关键~~~~~若是某些Bean提早被实例化,它就颇有可能不能被全部的`BeanPostProcessor`处理到了
		// 这也是咱们BeanPostProcessorChecker的做用,它就是检查这个而后输出日志的~
		String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);


		// 这个beanProcessorTargetCount此处赋值了,后续就都不会变了,BeanPostProcessorChecker就是和这个进行比较的~
		// beanFactory里面的Bean实例总个数+1(本身)+bean定义信息~
		int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
		// 把BeanPostProcessorChecker加进去,它其实就是作了一个检查而已~~~~~~~输出一个info日志~
		beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));
		
		// 一、找到全部实现PriorityOrdered的`BeanPostProcessor`,而后getBean,而后统一排序,而后beanFactory.addBeanPostProcessor()
		// 二、处理实现Ordered的,步骤同上
		// 三、处理没实现排序接口的普通的处理器,不须要sort了,直接add进去~
	
		// 最后注册一个特殊的处理器
		beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
	}

掌握这个顺序,是咱们后续解释上诉现象的根本基础。

采用@Autowired注入ApplicationContext和实现接口ApplicationContextAware的区别

在绝大多数状况下,这两种使用方式是等价的,都可以方便的获取到Spring容器上下文:ApplicationContext,可是在某些状况下,是有区别的,好比在以下状况下:

// 和上面惟一区别是 此处实现的是`PriorityOrdered`接口,上面就是普通的Ordered接口
@Slf4j
@Component
public class MyBeanPostProcessor implements BeanPostProcessor, PriorityOrdered{
	@Autowired
    private ApplicationContext applicationContext;
    ...
}

这样子注入,最终applicationContext的值为null。为什么呢?其实这就和BeanPostProcessor的加载时机以及@Autowired的执行时机有关,下面经过两张截图能够清晰的看到顺序:
此图表示执行到此处,容器内已经存在的BeanPostProcessor实例(注意已是实例):
在这里插入图片描述
此图表示BeanPostProcessor的定义信息们,他们等待被实例化:
在这里插入图片描述
下面对此几个BeanPostProcessor的Bean定义信息作分析以下:

  • AutowiredAnnotationBeanPostProcessor它实现了PriorityOrdered接口
  • CommonAnnotationBeanPostProcessor它也实现了PriorityOrdered接口
  • AsyncAnnotationBeanPostProcessor没有实现任何Orderd排序接口
  • MyBeanPostProcessor此处咱们让它实现了PriorityOrdered接口===========

从上面能够看到,由于实现了PriorityOrdered接口的BeanPostProcessor属于于同一级别,都是先统一调用getBean()实例化后被统一addBeanPostProcessor

所以AutowiredAnnotationBeanPostProcessor这个后置处理器并不能做用在咱们的MyBeanPostProcessor上面给咱们的属性赋值(由于他俩是同一级别的),由于根本就木有生效嘛~~~~可是像咱们第一个例子,MyBeanPostProcessor只是实现了普通的Ordered接口,优先级比PriorityOrdered低,因此就实现了正常的注入(由于高优先级的处理器先辈实例化了,因此能够做用于低优先级的bean了)~

由上可知,咱们在注册BeanPostProcessor的时候,他们的优先级的层级原则是须要注意的:高优先级的Bean可以做用于低有衔接的,反之不成立。可是同优先级的Bean不能相互做用~

如果实现ApplicationContextAware接口的话,ApplicationContext无论咋样均可以被正常获取到。道理也是同样的,是由于这个接口是被ApplicationContextAwareProcessor来解析的,而它已经早早被放进了Spring容器里面,因此经过实现接口的方式任什么时候候都是阔仪的

那平时到底使用@Autowired注入仍是实现接口ApplicationContextAware呢?

建议平时可以使用@Autowired注入时(我更建议使用构造器注入,这样彻底不与Spring容器耦合),就尽可能使用注入的方式。而不是去实现实现ApplicationContextAware接口的方式,由于这种方式属于与Spring容器强耦合的方式。(固然很是特殊状况下,只能使用ApplicationContextAware,好比上面那个状况)

@Async异步注解失效得缘由

相信到了此处,@Async失效的缘由已经不用再详细阐述一遍了。简单的说就是由于HelloService该Bean被提早初始化了,而这个时候AsyncAnnotationBeanPostProcessor根本就还没起做用(由于它仅仅是一个普通的BeanPostProcessor,加载是靠后的),因此确定也就不能扫描到@Async这种`注解方法,从而就不能生成代理对象,那就天然而然就失效了~

想说明的是,本文说明的是一类问题,而不是@Async这一个问题,请你们可以触类旁通

关于BeanPostProcessorChecker

首先它是一个BeanPostProcessor,是PostProcessorRegistrationDelegate的一个private static内部类,它的做用就是在postProcessAfterInitialization里检查做用在此Bean上的BeanPostProcessor够不够数,若是不够数(通常是提早加载了,或者被auto-proxying了这种状况就输出一条info日志~~~)

private static final class BeanPostProcessorChecker implements BeanPostProcessor {

		private static final Log logger = LogFactory.getLog(BeanPostProcessorChecker.class);

		// 从if条件能够看出哪些是不须要检测的Bean
		// 一、BeanPostProcessor类型不检测
		// 二、ROLE_INFRASTRUCTURE这种类型的Bean不检测 (Spring本身的Bean)
		@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;
		}

		private boolean isInfrastructureBean(@Nullable String beanName) {
			if (beanName != null && this.beanFactory.containsBeanDefinition(beanName)) {
				BeanDefinition bd = this.beanFactory.getBeanDefinition(beanName);
				return (bd.getRole() == RootBeanDefinition.ROLE_INFRASTRUCTURE);
			}
			return false;
		}
	}

若是你的bean被提早加载了,那就会看到这么一条日志:

Bean 'XXX' of type [XXXX] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

好比咱们项目中使用到了Feign面向接口进行远程调用,每次项目启动的时候,就会输出大量的相似日志,以下图:
在这里插入图片描述
Tips:
通常的若是你的Config类是一个XXXConfigurer的扩展配置类,也会打印相似的消息:

o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker - Bean 'asyncConfig' of type [com.config.AsyncConfig$$EnhancerBySpringCGLIB$$d4dbccee] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

根本缘由也是上面说的。具体到代码这里简单分析一下(此处以AsyncConfigurer为例):

// 在对它(AsyncAnnotationBeanPostProcessor)进行实例化的时候(它是个BeanPostProcessor,因此会根据bean定义进行getBean())
// 因此就必须先实例化它@Bean所在的类:
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
	@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public AsyncAnnotationBeanPostProcessor asyncAdvisor() { ... }
}

// 而后它的父类`AbstractAsyncConfiguration`里有个@Autowired方法:
@Configuration
public abstract class AbstractAsyncConfiguration implements ImportAware {
	@Autowired(required = false)
	void setConfigurers(Collection<AsyncConfigurer> configurers) { ... }
}
// 这个依赖注入就会去容器内找全部的`AsyncConfigurer`的实现,因此就把`AsyncConfig`给提早实例化了
// 因此打印了那么一句日志,不要感受到惊奇。这也是为什么spring这个checker里使用的日志级别是Info,而不是debug,更不是worn。
// 由于它Spring认为这个debug过轻了,可是warn又过重了,由于绝大部分状况下它都不影响程序的正常work~

注意避免BeanPostProcessor启动时对依赖的Bean形成误伤

BeanPostProcessor实例化时,自动依赖注入根据类型得到须要注入的Bean时,会将某些符合条件的Bean先实例化,若是此FacotryBean又依赖其余普通Bean,会致使该Bean提早启动,形成"误伤"(没法享受部分BeanPostProcessor的后置处理,例如典型的auto-proxy)。

知识交流

在这里插入图片描述
若群二维码失效,请加微信号(或者扫描下方二维码):fsx641385712。
而且备注:“java入群” 字样,会手动邀请入群

在这里插入图片描述