生命太短暂,不要去做一些基本没有人想要的货色。本文已被 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 多位小伙伴问过我此问题,可见一斑。
@Configuration
class 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 关键字
@Bean
static BeanPostProcessor postProcessor() {return new MyBeanPostProcessor();
}
运行程序,后果输入:
MyBeanPostProcessor init...
...
AppConfig init...
...
那个烦人的 BeanPostProcessorChecker
日志就不见了,清新了很多。同时亦可发现 AppConfig
是在 MyBeanPostProcessor
之后实例化的,这才合乎咱们所想的“失常”逻辑嘛。
正告二:Configuration 配置类加强失败
这个“正告”就比上一个重大得多了,它有 极大的可能 导致你程序谬误,并且你还 很难定位 问题所在。
@Configuration
class 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() = 1300528434
son init...hashCode() = 1598434875
Parent 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();
//}
@Bean
static BeanDefinitionRegistryPostProcessor postProcessor() {return new MyBeanDefinitionRegistryPostProcessor();
}
运行程序,后果输入:
MyBeanDefinitionRegistryPostProcessor init...
...
AppConfig init...
son init...hashCode() = 2090289474
Parent init...
...
完满。
正告三:非动态 @Bean 办法导致 @Autowired 等注解生效
@Configuration
class 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() = 882706486
Parent 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();
//}
@Bean
static BeanFactoryPostProcessor postProcessor() {return new MyBeanFactoryPostProcessor();
}
运行程序,后果输入:
MyBeanFactoryPostProcessor init...
AppConfig init...
son init...hashCode() = 1906549136
Parent init...
// @PostConstruct 注解失效喽
AppConfig.parent = com.yourbatman.bean.Parent@baf1bb3
...
世界一下子又清新了有木有。
起因总结
以上三个 case 是有共同点的,粗略的讲导致它们的起因甚至是同一个:AppConfig 这个 Bean 被过早初始化 。然而咱们的解决方案仿佛也是同一个: 应用 static 晋升 Bean 的优先级。
那么为何 AppConfig 会被提前初始化呢?为何应用 static 关键字就没有问题了呢?根本原因可提前剧透:static 静态方法属于类,执行静态方法时并不需要初始化所在类的实例;而实例办法属于实例,执行它时必须先初始化所在类的实例。听起来是不是十分的简略,JavaSE 的货色嘛,当然只通晓到这个档次必定是远远不够的,限于篇幅起因,对于 Spring 是如何解决的 源码级别的剖析 我放在了下篇文章,请别走开哟~
static 静态方法肯定优先执行吗?
看完本文,有些小伙伴就忍不住蠢蠢欲动了,甚至很果断的得出结论:static 标注的 @Bean 办法优先级更高,其实这是谬误的,比方你看如下示例:
@Configuration
class 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();
//}
@Bean
static 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 的乌托邦 |
每日文章举荐 | 每日文章举荐 |