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

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 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日志并进行多维度分析。
相关文章
|
7月前
|
Java 程序员
Java多态:谁说“一刀切”最省事?
【6月更文挑战第17天】Java的多态性打破了“一刀切”的编程模式,允许根据不同对象类型执行特定操作。通过抽象类`Product`和其子类`Book`、`Electronics`、`Clothing`,展示了如何利用多态来定制商品的`displayDetails()`方法。这样,代码保持简洁,每个商品类型能展示独特信息,如书籍的作者、电子产品的保修政策和服装的尺码。多态使代码更具适应性和灵活性,提高了解决方案的质量和效率。
35 0
|
7月前
|
Java Apache Spring
面试官:如何自定义一个工厂类给线程池命名,我:现场手撕吗?
【6月更文挑战第3天】面试官:如何自定义一个工厂类给线程池命名,我:现场手撕吗?
42 0
|
8月前
|
Java
Java面向对象编程:就近原则与this关键字详解
Java面向对象编程:就近原则与this关键字详解
81 0
|
8月前
|
存储 编译器 程序员
【C++学习】类和对象(中)一招带你彻底了解六大默认成员函数
【C++学习】类和对象(中)一招带你彻底了解六大默认成员函数
102 0
|
8月前
|
设计模式 Java 数据库
二十三种设计模式全面解析-单例设计模式:解密全局独一无二的实例创造者
二十三种设计模式全面解析-单例设计模式:解密全局独一无二的实例创造者
|
安全 Java 编译器
《我要进大厂》- Java基础夺命连环10问,你能坚持到第几问?(异常 | 泛型)
《我要进大厂》- Java基础夺命连环10问,你能坚持到第几问?(异常 | 泛型)
《我要进大厂》- Java基础夺命连环10问,你能坚持到第几问?(异常 | 泛型)
|
XML 安全 Java
超车时刻:Java反射源码解析
超车时刻:Java反射源码解析
166 0
超车时刻:Java反射源码解析
|
Java 中间件 程序员
static关键字真能提高Bean的优先级吗?答:真能(上)
static关键字真能提高Bean的优先级吗?答:真能(上)
static关键字真能提高Bean的优先级吗?答:真能(上)
|
存储 安全 Java
static关键字真能提高Bean的优先级吗?答:真能(下)
static关键字真能提高Bean的优先级吗?答:真能(下)
static关键字真能提高Bean的优先级吗?答:真能(下)
|
编译器 C++
<C++>一篇文章搞懂类和对象中常函数和常对象的实质以及避免空指针访问的小妙招
<C++>一篇文章搞懂类和对象中常函数和常对象的实质以及避免空指针访问的小妙招
170 0