生命太短暂,不要去做一些基本没有人想要的货色。本文已被 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......

后果剖析(问题点/冲突点):

  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关键字@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...

后果剖析(问题点/冲突点):

  1. AppConfig居然比MyBeanDefinitionRegistryPostProcessor的初始化机会还早,这本就不合理
  2. ConfigurationClassPostProcessor 的日志中可看到:AppConfig配置类enhance加强失败
  3. 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...

后果剖析(问题点/冲突点):

  1. AppConfig提前于MyBeanFactoryPostProcessor初始化
  2. @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关键字的应用做出如下阐明,供以参考:

  1. 对于一般类型(非后置处理器类型)的@Bean办法,应用static关键字并不能扭转程序(依照办法定义程序执行),所以别指望它
  2. static关键字个别有且仅用于@Bean办法返回为BeanPostProcessorBeanFactoryPostProcessor等类型的办法,并且倡议此种办法请务必应用static润饰,否则容易导致隐患,埋雷

static关键字不要滥用(其实任何关键字皆勿乱用),在同一配置类内,与其说它是晋升了Bean的优先级,倒不如说它让@Bean办法动态化从而不再须要依赖所在类的实例即可独立运行。另外咱们晓得,static要害还能够润饰(外部)类,那么如果放在类上它又是什么体现呢?同样的,你先思考,下篇文章咱们接着聊~

阐明:应用static润饰Class类在Spring Boot主动配置类里特地特地常见,所以把握起来很具价值

思考题:

明天的思考题比较简单:为何文首三种case的正告信息都是info级别呢?是否有级别过低之嫌?


总结

本文还是蛮干的哈,不出意外它可能帮你解决你工程中的某些问题,排除掉一些隐患,毕竟墨菲定律被验证了你放心的事它总会产生,防患于未然能力把本人置于平安洼地嘛。

你可能惊讶,A哥竟能把static关键字在Spring中的利用都能写出个专栏进去,是的,这不是就是本公众号的定位么 ,小而美和回绝浅尝辄止嘛。对于一些常识(比方本文的static关键字的应用)我并不推崇强行记忆,因为那真的很容易忘,疾速应用能够简略记记,但真想记得牢(甚至成为永恒记忆),那必须得去深水区看看。来吧,下文将授之以渔~

很多小伙伴去强行记忆Spring Boot反对的那17种内部化配置,此时你应该问本人:当初你可能记得,一周当前呢?一个月当前呢?所以你须要另辟蹊径,那就继续关注我吧????

关注A哥

AuthorA哥(YourBatman)
集体站点www.yourbatman.cn
E-mailyourbatman@qq.com
微 信fsx641385712
沉闷平台
公众号BAT的乌托邦(ID:BAT-utopia)
常识星球BAT的乌托邦
每日文章举荐每日文章举荐