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

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

这三步结果环环相扣,因为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类型,那就输出此警告日志来提醒使用者要当心。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
12月前
|
Java
final关键字:我偏不让你继承
final关键字:我偏不让你继承
|
缓存 安全 Java
12000+字Java反射,一起全面了解Java反射机制,为学习框架铺路
12000+字Java反射,一起全面了解Java反射机制,为学习框架铺路
84 0
12000+字Java反射,一起全面了解Java反射机制,为学习框架铺路
|
前端开发 Java 关系型数据库
面试突击79:Bean 作用域是啥?它有几种类型?
面试突击79:Bean 作用域是啥?它有几种类型?
190 0
|
Java 数据库连接 API
用这种方式实现单例,绝对能加分!
单例这个东西,可是说是Java基础面试必考问题,不管大小公司,提问率极高,有的面试官甚至直接让你写出具体的代码实现。所以,搞清楚单例的实现并且懂得接下来所说的,绝对能够让面试官对你有更好的印象。
|
存储 安全 Java
static关键字真能提高Bean的优先级吗?答:真能(下)
static关键字真能提高Bean的优先级吗?答:真能(下)
static关键字真能提高Bean的优先级吗?答:真能(下)
|
Java 中间件 程序员
static关键字真能提高Bean的优先级吗?答:真能(上)
static关键字真能提高Bean的优先级吗?答:真能(上)
static关键字真能提高Bean的优先级吗?答:真能(上)
|
编译器 C# C++
我竟然用它搞懂了王者荣耀的技能释放机制!【C#委托】
我竟然用它搞懂了王者荣耀的技能释放机制!【C#委托】
211 0
|
Java 编译器
你一直在使用的static关键字你真的懂吗?
你一直在使用的static关键字你真的懂吗?
|
Java
编写Java程序,中国道教中掌管天宫的最高权力统治者是玉帝(Emperor),我们可以认为玉帝是一个单例模式,在这个场景中,有玉帝和天宫的大臣(Minister)们,大臣每天要上朝参见玉帝,而每一天参
编写Java程序,中国道教中掌管天宫的最高权力统治者是玉帝(Emperor),我们可以认为玉帝是一个单例模式,在这个场景中,有玉帝和天宫的大臣(Minister)们,大臣每天要上朝参见玉帝,而每一天参
206 0
编写Java程序,中国道教中掌管天宫的最高权力统治者是玉帝(Emperor),我们可以认为玉帝是一个单例模式,在这个场景中,有玉帝和天宫的大臣(Minister)们,大臣每天要上朝参见玉帝,而每一天参
|
设计模式 Java API
你自我介绍说很懂Spring配置类,那你怎么解释这个现象?(中)
你自我介绍说很懂Spring配置类,那你怎么解释这个现象?(中)
你自我介绍说很懂Spring配置类,那你怎么解释这个现象?(中)

热门文章

最新文章