生命太短暂,不要去做一些基本没有人想要的货色。本文已被 https://www.yourbatman.cn 收录,外面一并有Spring技术栈、MyBatis、JVM、中间件等小而美的专栏供以收费学习。关注公众号【BAT的乌托邦】一一击破,深刻把握,回绝浅尝辄止。
前言
各位小伙伴大家好,我是A哥。对于Spring初始化Bean的程序问题,是个陈词滥调的话题了,论断可总结为一句话:全局无序,部分有序。Spring Bean
整体上是无序的,而事实是大多数状况下咱们真的无需关怀,无序就无序呗,无所谓喽。然而(此处应该有然而哈),我有理由置信,对于有肯定从业教训的Javaer来说,或多或少都经验过Bean初始化程序带来的“困扰”,兴许是因为没有对你的性能造成影响,兴许可能是你全然“不知情”,所以最终就不了了之~
隐患终归隐患,按照墨菲定律来讲,放心的事它总归是会产生的。A哥常常“唆使”程序员要面向工资编程,尽管这价值观有点扭曲,但不可否认很多小伙伴真是这么想的(命中你了没有????),稍加掩饰了而已。话粗理不粗哦,almost所有的Javaer都在用Spring,你凭什么工资比你身边共事的高呢?
Spring对Bean的(生命周期)治理是它最为外围的能力,同时也是很简单、很难把握的一个知识点。当初就能够启动你的工程,有木有这句日志:
"Bean 'xxx' of type [xxxx] is not eligible for getting processed by all BeanPostProcessors" + "(for example: not eligible for auto-proxying)"
这是一个典型的Spring Bean过早初始化问题,搜搜看你日志里是否有此句喽。这句日志是由Spring的BeanPostProcessorChecker
这个类负责输入,含意为:你的Bean xxx不能被所有的BeanPostProcessors
解决到(有的生命周期触达不到),揭示你留神。此句日志在低些的版本里是warn正告级别,在本文约定的版本里官网把它改为了info级别。
绝大多数状况下,此句日志的输入不会对你的性能造成影响,因而无需搭理。这也是Spring官网为何把它从warn调低为info级别的起因
我在CSDN上写过一篇“Spring Bean过早初始化导致的误伤”的文章,访问量达近4w:
从这个数据(访问量)上来看,这件事“并不简略”,遇到此麻烦的小伙伴不在少数且的确难倒了一众人。对于Spring Bean的程序,全局是不可控的,然而部分上它提供了多种形式来不便使用者进步/升高优先级(比方后面的应用@AutoConfigureBefore调整配置程序竟没失效?这篇文章),本文就聊聊static关键字对于提供Bean的优先级的效用。
版本约定
本文内容若没做非凡阐明,均基于以下版本:
- JDK:
1.8
- Spring Framework:
5.2.2.RELEASE
注释
本文采纳从 问题提出-后果剖析-解决方案-原理分析 这4个步骤,层层递进的去感触static关键字在Spring Bean上的魅力~
正告一:来自BeanPostProcessorChecker
这是最为常见的一种正告,特地当你的工程应用了shiro
做鉴权框架的时候。在我记忆中这一年来有N多位小伙伴问过我此问题,可见一斑。
@Configurationclass AppConfig { AppConfig() { System.out.println("AppConfig init..."); } @Bean BeanPostProcessor postProcessor() { return new MyBeanPostProcessor(); }}class MyBeanPostProcessor implements BeanPostProcessor { MyBeanPostProcessor() { System.out.println("MyBeanPostProcessor init..."); }}
运行程序,输入后果:
AppConfig init...2020-05-31 07:40:50.979 INFO 15740 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'appConfig' of type [com.yourbatman.config.AppConfig$$EnhancerBySpringCGLIB$$29b523c8] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)MyBeanPostProcessor init......
后果剖析(问题点/冲突点):
AppConfig
优先于MyBeanPostProcessor
进行实例化- 常识是:
MyBeanPostProcessor
作为一个后置处理器理当是先被初始化的,而AppConfig
仅仅是个一般Bean而已,初始化理当靠后
- 常识是:
呈现了
BeanPostProcessorChecker
日志:示意AppConfig
这个Bena不能被所有的BeanPostProcessors解决,所以有可能会让它“错过”容器对Bean的某些生命周期治理,因而可能损失某些能力(比方不能被主动代理),存在隐患- 凡是只有你工程里呈现了
BeanPostProcessorChecker
输入日志,理当都得引起你的留神,因为这属于Spring的正告日志(尽管新版本已下调为了info级别)
- 凡是只有你工程里呈现了
阐明:这是一个Info日志,并非warn/error级别。绝大多数状况下你的确无需关注,然而如果你是一个容器开发者,倡议请务必解决此问题(毕竟貌似大多数中间件开发者都有肯定代码洁癖????)
解决方案:static关键字晋升优先级
基于上例,咱们仅需做如下小改变:
AppConfig://@Bean//BeanPostProcessor postProcessor() {// return new MyBeanPostProcessor();//}// 办法后面加上static关键字@Beanstatic BeanPostProcessor postProcessor() { return new MyBeanPostProcessor();}
运行程序,后果输入:
MyBeanPostProcessor init......AppConfig init......
那个烦人的BeanPostProcessorChecker
日志就不见了,清新了很多。同时亦可发现AppConfig
是在MyBeanPostProcessor
之后实例化的,这才合乎咱们所想的“失常”逻辑嘛。
正告二:Configuration配置类加强失败
这个“正告”就比上一个重大得多了,它有极大的可能导致你程序谬误,并且你还很难定位问题所在。
@Configurationclass AppConfig { AppConfig() { System.out.println("AppConfig init..."); } @Bean BeanDefinitionRegistryPostProcessor postProcessor() { return new MyBeanDefinitionRegistryPostProcessor(); } /////////////////////////////// @Bean Son son(){ return new Son(); } @Bean Parent parent(){ return new Parent(son()); }}class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { MyBeanDefinitionRegistryPostProcessor() { System.out.println("MyBeanDefinitionRegistryPostProcessor init..."); } @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { }}
运行程序,后果输入:
AppConfig init...MyBeanDefinitionRegistryPostProcessor init...2020-05-31 07:59:06.363 INFO 37512 --- [ main] o.s.c.a.ConfigurationClassPostProcessor : Cannot enhance @Configuration bean definition 'appConfig' since its singleton instance has been created too early. The typical cause is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor return type: Consider declaring such methods as 'static'....son init...hashCode() = 1300528434son init...hashCode() = 1598434875Parent init...
后果剖析(问题点/冲突点):
- AppConfig居然比MyBeanDefinitionRegistryPostProcessor的初始化机会还早,这本就不合理
- 从
ConfigurationClassPostProcessor
的日志中可看到:AppConfig配置类enhance加强失败 - Son对象居然被创立了两个不同的实例,这将会间接导致功能性谬误
这三步后果环环相扣,因为1导致了2的加强失败,因为2的加强失败导致了3的创立多个实例,真堪称一步错,步步错。须要留神的是:这里ConfigurationClassPostProcessor输入的仍旧是info日志(我集体认为,Spring把这个输入调整为warn级别是更为正当的,因为它影响较大)。
阐明:对这个后果的了解基于对Spring配置类的了解,因而强烈建议你进我公众号参阅那个可能是写的最全、最好的Spring配置类专栏学习(文章不多,6篇足矣)
源码处解释:
ConfigurationClassPostProcessor:// 对Full模式的配置类尝试应用CGLIB字节码晋升public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) { ... // 对Full模式的配置类有个判断/校验 if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) { if (!(beanDef instanceof AbstractBeanDefinition)) { throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" + beanName + "' since it is not stored in an AbstractBeanDefinition subclass"); } // 若判断发现此时该配置类曾经是个单例Bean了(阐明已初始化实现) // 那就不再做解决,并且输入正告日志告知使用者(尽管是info日志) else if (logger.isInfoEnabled() && beanFactory.containsSingleton(beanName)) { logger.info("Cannot enhance @Configuration bean definition '" + beanName + "' since its singleton instance has been created too early. The typical cause " + "is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " + "return type: Consider declaring such methods as 'static'."); } configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef); } ...}
因为配置类加强是在BeanFactoryPostProcessor#postProcessBeanFactory()
申明周期阶段去做的,而BeanDefinitionRegistryPostProcessor
它会优先于该步骤实现实例化(其实次要是优先级比BeanFactoryPostProcessor
高),从而间接带动 AppConfig提前初始化导致了问题,这便是根本原因所在。
发问点:本处应用了个自定义的BeanDefinitionRegistryPostProcessor
模仿了成果,那如果你是应用的BeanFactoryPostProcessor
能进去这个成果吗???答案是不能的,具体起因留给读者思考,可参考:PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors
这段流程辅助了解。
解决方案:static关键字晋升优先级
来吧,持续应用static关键字革新一下:
AppConfig://@Bean//BeanDefinitionRegistryPostProcessor postProcessor() {// return new MyBeanDefinitionRegistryPostProcessor();//}@Beanstatic BeanDefinitionRegistryPostProcessor postProcessor() { return new MyBeanDefinitionRegistryPostProcessor();}
运行程序,后果输入:
MyBeanDefinitionRegistryPostProcessor init......AppConfig init...son init...hashCode() = 2090289474Parent init......
完满。
正告三:非动态@Bean办法导致@Autowired等注解生效
@Configurationclass AppConfig { @Autowired private Parent parent; @PostConstruct void init() { System.out.println("AppConfig.parent = " + parent); } AppConfig() { System.out.println("AppConfig init..."); } @Bean BeanFactoryPostProcessor postProcessor() { return new MyBeanFactoryPostProcessor(); } @Bean Son son() { return new Son(); } @Bean Parent parent() { return new Parent(son()); }}class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { MyBeanFactoryPostProcessor() { System.out.println("MyBeanFactoryPostProcessor init..."); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { }}
运行程序,后果输入:
AppConfig init...2020-05-31 08:28:06.550 INFO 1464 --- [ main] o.s.c.a.ConfigurationClassEnhancer : @Bean method AppConfig.postProcessor is non-static and returns an object assignable to Spring's BeanFactoryPostProcessor interface. This will result in a failure to process annotations such as @Autowired, @Resource and @PostConstruct within the method's declaring @Configuration class. Add the 'static' modifier to this method to avoid these container lifecycle issues; see @Bean javadoc for complete details.MyBeanFactoryPostProcessor init......son init...hashCode() = 882706486Parent init...
后果剖析(问题点/冲突点):
- AppConfig提前于
MyBeanFactoryPostProcessor
初始化 @Autowired/@PostConstruct
等注解没有失效,这个问题很大
须要强调的是:此时的AppConfig是被enhance加强胜利了的,这样才有可能进入到BeanMethodInterceptor
拦挡外面,才有可能输入这句日志(该拦截器会拦挡Full模式配置列的所有的@Bean办法的执行)
这句日志由ConfigurationClassEnhancer.BeanMethodInterceptor
输入,含意为:你的@Bean标注的办法是非static的并且返回了一个BeanFactoryPostProcessor
类型的实例,这就导致了配置类外面的@Autowired, @Resource,@PostConstruct
等注解都将得不到解析,这是比拟危险的(所以其实这个日志调整为warn级别也是阔仪的)。
小细节:为毛日志看起来是ConfigurationClassEnhancer这个类输入的呢?这是因为BeanMethodInterceptor
是它的动态外部类,和它共用的一个logger
源码处解释:
ConfigurationClassEnhancer.BeanMethodInterceptor: if (isCurrentlyInvokedFactoryMethod(beanMethod)) { if (logger.isInfoEnabled() && BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) { logger.info(String.format("@Bean method %s.%s is non-static and returns an object " + "assignable to Spring's BeanFactoryPostProcessor interface. This will " + "result in a failure to process annotations such as @Autowired, " + "@Resource and @PostConstruct within the method's declaring " + "@Configuration class. Add the 'static' modifier to this method to avoid " + "these container lifecycle issues; see @Bean javadoc for complete details.", beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName())); } return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs); }
解释为:如果以后正在执行的@Bean办法(铁定不是static,因为静态方法它也拦挡不到嘛)返回类型是BeanFactoryPostProcessor
类型,那就输入此正告日志来揭示使用者要当心。
解决方案:static关键字晋升优先级
AppConfig://@Bean//BeanFactoryPostProcessor postProcessor() {// return new MyBeanFactoryPostProcessor();//}@Beanstatic BeanFactoryPostProcessor postProcessor() { return new MyBeanFactoryPostProcessor();}
运行程序,后果输入:
MyBeanFactoryPostProcessor init...AppConfig init...son init...hashCode() = 1906549136Parent init...// @PostConstruct注解失效喽AppConfig.parent = com.yourbatman.bean.Parent@baf1bb3...
世界一下子又清新了有木有。
起因总结
以上三个case是有共同点的,粗略的讲导致它们的起因甚至是同一个:AppConfig这个Bean被过早初始化。然而咱们的解决方案仿佛也是同一个:应用static晋升Bean的优先级。
那么为何AppConfig会被提前初始化呢?为何应用static关键字就没有问题了呢?根本原因可提前剧透:static静态方法属于类,执行静态方法时并不需要初始化所在类的实例;而实例办法属于实例,执行它时必须先初始化所在类的实例。听起来是不是十分的简略,JavaSE的货色嘛,当然只通晓到这个档次必定是远远不够的,限于篇幅起因,对于Spring是如何解决的源码级别的剖析我放在了下篇文章,请别走开哟~
static静态方法肯定优先执行吗?
看完本文,有些小伙伴就忍不住蠢蠢欲动了,甚至很果断的得出结论:static标注的@Bean办法优先级更高,其实这是谬误的,比方你看如下示例:
@Configurationclass AppConfig2 { AppConfig2(){ System.out.println("AppConfig2 init..."); } @Bean Son son() { return new Son(); } @Bean Daughter daughter() { return new Daughter(); } @Bean Parent Parent() { return new Parent(); }}
运行程序,后果输入:
AppConfig2 init...son init...Daughter init...Parent init...
这时候你想让Parent在Son之前初始化,因而你想着在用static关键字来晋升优先级,这么做:
AppConfig2://@Bean//Parent Parent() {// return new Parent();//}@Beanstatic Parent Parent() { return new Parent();}
后果:你徒劳了,static貌似并没有失效,怎么回事?
起因浅析
为了满足你的好奇心,这里给个浅析,道出关键因素。咱们晓得@Bean办法(不论是静态方法还是实例办法)最终都会被封装进ConfigurationClass
实例外面,应用Set<BeanMethod> beanMethods
存储着,关键点在于它是个LinkedHashSet
所以是有序的(寄存程序),而存入的程序底层是由clazz.getDeclaredMethods()
来决定的,由此可知@Bean办法执行程序和有无static没有半毛钱关系。
阐明:clazz.getDeclaredMethods()
失去的是Method[]数组,是有序的。这个程序由字节码(定义程序)来保障:先定义,先服务。
由此可见,static并不是真正意义上的进步Bean优先级,对于如上你的需要case,你能够应用@DependsOn
注解来保障,它也是和Bean程序非亲非故的一个注解,在本专栏后续文章中将会具体讲到。
所以对于@Bean办法的执行程序的正确论断应该是:在同一配置类内,在无其它“烦扰”状况下(无@DependsOn、@Lazy等注解
),@Bean办法的执行程序听从的是定义程序(后置处理器类型除外)。
小发问:如果是垮@Configuration配置类的状况,程序如何界定呢?那么这就不是同一层级的问题了,首先思考的应该是@Configuration配置类的程序问题,后面有文章提到过配置类是反对无限的的@Order注解排序的,具体分析请仍旧放弃关注A哥后续文章详解哈...
static关键字应用注意事项
在同一个@Configuration
配置类内,对static关键字的应用做出如下阐明,供以参考:
- 对于一般类型(非后置处理器类型)的@Bean办法,应用static关键字并不能扭转程序(依照办法定义程序执行),所以别指望它
- static关键字个别有且仅用于@Bean办法返回为
BeanPostProcessor
、BeanFactoryPostProcessor
等类型的办法,并且倡议此种办法请务必应用static润饰,否则容易导致隐患,埋雷
static关键字不要滥用(其实任何关键字皆勿乱用),在同一配置类内,与其说它是晋升了Bean的优先级,倒不如说它让@Bean办法动态化从而不再须要依赖所在类的实例即可独立运行。另外咱们晓得,static要害还能够润饰(外部)类,那么如果放在类上它又是什么体现呢?同样的,你先思考,下篇文章咱们接着聊~
阐明:应用static润饰Class类在Spring Boot主动配置类里特地特地常见,所以把握起来很具价值
思考题:
明天的思考题比较简单:为何文首三种case的正告信息都是info级别呢?是否有级别过低之嫌?
总结
本文还是蛮干的哈,不出意外它可能帮你解决你工程中的某些问题,排除掉一些隐患,毕竟墨菲定律被验证了你放心的事它总会产生,防患于未然能力把本人置于平安洼地嘛。
你可能惊讶,A哥竟能把static关键字在Spring中的利用都能写出个专栏进去,是的,这不是就是本公众号的定位么 ,小而美和回绝浅尝辄止嘛。对于一些常识(比方本文的static关键字的应用)我并不推崇强行记忆,因为那真的很容易忘,疾速应用能够简略记记,但真想记得牢(甚至成为永恒记忆),那必须得去深水区看看。来吧,下文将授之以渔~
很多小伙伴去强行记忆Spring Boot反对的那17种内部化配置,此时你应该问本人:当初你可能记得,一周当前呢?一个月当前呢?所以你须要另辟蹊径,那就继续关注我吧????
关注A哥
Author | A哥(YourBatman) |
---|---|
集体站点 | www.yourbatman.cn |
yourbatman@qq.com | |
微 信 | fsx641385712 |
沉闷平台 | |
公众号 | BAT的乌托邦(ID:BAT-utopia) |
常识星球 | BAT的乌托邦 |
每日文章举荐 | 每日文章举荐 |