祝你们五一国际劳动节快乐~你是在快乐的撸码,仍是在快乐的浪呢?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
是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的功能,从而致使启动时的"误伤"
。
可能有的人会有疑问,为何你这里(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
失效的缘由已经不用再详细阐述一遍了。简单的说就是由于HelloService
该Bean被提早初始化了,而这个时候AsyncAnnotationBeanPostProcessor
根本就还没起做用(由于它仅仅是一个普通的BeanPostProcessor
,加载是靠后的),因此确定也就不能扫描到@Async
这种`注解方法,从而就不能生成代理对象,那就天然而然就失效了~
想说明的是,本文说明的是一类问题,而不是@Async这一个问题,请你们可以触类旁通
首先它是一个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时,会将某些符合条件的Bean先实例化,若是此FacotryBean又依赖其余普通Bean
,会致使该Bean提早启动
,形成"误伤"
(没法享受部分BeanPostProcessor
的后置处理,例如典型的auto-proxy)。
若群二维码失效,请加微信号(或者扫描下方二维码):fsx641385712。
而且备注:“java入群” 字样,会手动邀请入群