乐趣区

static关键字真能提高Bean的优先级吗答真能

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

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

  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 对象居然被创立了 两个 不同的实例,这将会间接导致功能性谬误

这三步后果环环相扣,因为 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...

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

  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();
//}

@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 关键字的应用做出如下阐明,供以参考:

  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 哥

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

退出移动版