static关键字真能提高Bean的优先级吗?答:真能(上)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: static关键字真能提高Bean的优先级吗?答:真能(上)

前言


关于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:


image.png

从这个数据(访问量)上来看,这件事“并不简单”,遇到此麻烦的小伙伴不在少数且确实难倒了一众人。关于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...
...


结果分析(问题点/冲突点):


1.AppConfig优先于MyBeanPostProcessor进行实例化

  1. 常识是:MyBeanPostProcessor作为一个后置处理器理应是先被初始化的,而AppConfig仅仅是个普通Bean而已,初始化理应靠后


2.出现了BeanPostProcessorChecker日志:表示AppConfig这个Bena不能被所有的BeanPostProcessors处理,所以有可能会让它“错过”容器对Bean的某些生命周期管理,因此可能损失某些能力(比如不能被自动代理),存在隐患

  1. 但凡只要你工程里出现了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...


结果分析(问题点/冲突点):


  1. AppConfig竟然比MyBeanDefinitionRegistryPostProcessor的初始化时机还早,这本就不合理
  2. 从ConfigurationClassPostProcessor的日志中可看到:AppConfig配置类enhance增强失败
  3. Son对象竟然被创建了两个不同的实例,这将会直接导致功能性错误
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
7月前
|
Java Apache Spring
面试官:如何自定义一个工厂类给线程池命名,我:现场手撕吗?
【6月更文挑战第3天】面试官:如何自定义一个工厂类给线程池命名,我:现场手撕吗?
44 0
|
8月前
|
算法
犯错总结--工厂模式和策略模式傻傻没分清
犯错总结--工厂模式和策略模式傻傻没分清
68 0
犯错总结--工厂模式和策略模式傻傻没分清
|
存储 安全 Java
static关键字真能提高Bean的优先级吗?答:真能(下)
static关键字真能提高Bean的优先级吗?答:真能(下)
static关键字真能提高Bean的优先级吗?答:真能(下)
|
编译器 C++
<C++>一篇文章搞懂类和对象中常函数和常对象的实质以及避免空指针访问的小妙招
<C++>一篇文章搞懂类和对象中常函数和常对象的实质以及避免空指针访问的小妙招
173 0
(二十)看完这篇类的实例化顺序,考执行顺序的面试题就难不倒你了
一段代码的执行顺序经常会放到面试题或者笔试题中,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,它们的执行顺序,关于这一类题目只要了解了类的实例化顺序,就不会再成为问题。先看一下下面的面试题
|
Java 数据库连接 API
用这种方式实现单例,绝对能加分!
单例这个东西,可是说是Java基础面试必考问题,不管大小公司,提问率极高,有的面试官甚至直接让你写出具体的代码实现。所以,搞清楚单例的实现并且懂得接下来所说的,绝对能够让面试官对你有更好的印象。
|
Java Spring
static关键字真能提高Bean的优先级吗?答:真能(中)
static关键字真能提高Bean的优先级吗?答:真能(中)
|
存储 安全 Java
static关键字真能提高Bean的优先级吗?答:真能
一个static关键字在Spring的使用,竟能写出一个专栏
682 0
static关键字真能提高Bean的优先级吗?答:真能
|
Java 数据库
后浪拍前浪-覆写父类方法 | 带你学《Java面向对象编程》之三十九
既然出现了继承的关系,那么就存在子类和父类的联系,而在子类之中有可能定义和父类完全相同的方法或属性的名称,这个时候就称为覆写。
后浪拍前浪-覆写父类方法   | 带你学《Java面向对象编程》之三十九
|
Java
你以为工厂模式很简单,可能是因为你懂的只是冰山的一角
工厂模式主要是为创建对象提供过渡接口,以便将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
19135 0