基于Spring框架由ConditionalOnMissingBean注解引发的问题

 更新时间:2023年11月27日 10:39:30   作者:爱艺欣聊编程  
这篇文章主要介绍了基于Spring框架由ConditionalOnMissingBean注解引发的问题,具有很好

问题描述

最新负责的工程在做dubbo配置disconf静态配置托管优化,由一个StaticConfigPropertiesFactoryBean来读取静态配置,它是PropertiesFactoryBean的一个子类,读取到的所有配置放在一个Properties中。

dubbo配置直接引用这个Properties的值。

像下面这样,dubbo接口的group通过disconf的静态配置项dubbo.hst.pay.group定义:

工程使用Spring Boot框架,Spring版本号4.1.1,Spring Boot版本号1.1.9,spring-security版本号4.0.2,启用了Spring Boot框架的Anto Configuration特性。

StaticConfigPropertiesFactoryBean这个bean的configSrc属性值是个占位符,值在外部配置文件定义。

但是启动工程时出现了一个问题,抛出了一个IllegalArgumentException异常,异常日志如下:

Caused by: java.lang.IllegalArgumentException: configSrc error : ${disconf.configSrc}
    at com.baidu.disconf.client.haitao.properties.StaticConfigPropertiesFactoryBean.createProperties(StaticConfigPropertiesFactoryBean.java:65)
    at org.springframework.beans.factory.config.PropertiesFactoryBean.afterPropertiesSet(PropertiesFactoryBean.java:71)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1633)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1570)
    ... 35 more

排查过程

从日志中看到configSrc这个属性的值不满足条件导致抛出了这个异常,而且值是${disconf.configSrc},这里比较奇怪,在初始化bean的时候框架不是会对占位符进行解析求职处理的么,什么原因导致占位符未被处理bean就开始初始化了?

我们知道Spring框架由PropertySourcesPlaceholderConfigurer来处理占位符,PropertySourcesPlaceholderConfigurer是一个BeanFactoryPostProcessor,在容器所有BeanDefinition被加载之后,bean初始化之前,BeanFactoryPostProcessor会被激活,PropertySourcesPlaceholderConfigurer会加载所有被引入的属性文件,并且查找占位符对应的值,并把对应的值设置到bean属性对应的PropertyValue中,这样在bean初始化时设置每个属性的值时设置的就是处理后的值了。

org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List)

就是这在触发这个BeanFactoryPostProcessor的,注意代码行数是162行。

但是现在的现象是configSrc这个属性初始化之后却是未处理的占位符字符串。

分析原因有两种可能,第一种disconf.configSrc这个配置项在文件中未定义或未被容器加载,第二种StaticConfigPropertiesFactoryBean在属性占位符处理之前就初始化了。

第一种原因很容易就排除了,disconf.configSrc这个配置项已经定义了,在这次修改之前就已存在之前一直都是没问题的,且由于ignoreUnresolvablePlaceholders这个设置默认是false,就算它未被定义容器也会提前抛出IllegalArgumentException异常:

org.springframework.util.PropertyPlaceholderHelper.parseStringValue(String, PlaceholderResolver, Set)

所以只能是第二种原因了,那就是StaticConfigPropertiesFactoryBean在属性占位符处理之前就被容器初始化了。Spring框架如此庞大,要找原因最有效的手段就是debug了,把断点打到StaticConfigPropertiesFactoryBean初始化代码处,看它是从哪进来的:

org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory, List)

通过debug发现,这个bean的初始化也是从BeanFactoryPostProcessor调进去的,而且看下代码是94行,在占位符处理的164行前面,好了,这里可以解释configSrc值为什么是错的。

但是这些BeanFactoryPostProcessor是干嘛的?为什么在其它BFPP激活之前就去初始化bean了。

看一下这个BFPP是什么:

这个BFPP是ConfigurationClassPostProcessor,当应用使用了annotation-config或component-scan扫描注解bean时,容器会自动注册一个ConfigurationClassPostProcessor来加载注解定义的BeanDefinition,按常理,这个BFPP也只会生成BeanDefinition,不会执行bean初始化动作。

那么是什么地方导致bean发生初始化了呢?

再看下上面哪个调用栈,在OnMissingBeanCondition.matches之后就调用BeanFactory的getBeansXXX了,然后一步一步触发了createBean。

那这个OnMissingBeanCondition从哪来的呢?

从debug的情况来看跟WebMvcSecurityConfiguration有关。

看下WebMvcSecurityConfiguration这个类,它有一个@ConditionalOnMissingBean注解,这个注解作用在bean定义上,它的作用就是在容器加载它作用的bean时,检查容器中是否存在目标类型(ConditionalOnMissingBean注解的value值)的bean了,如果存在这跳过原始bean的BeanDefinition加载动作。

上面说的那段逻辑就是由OnMissingBeanCondition.matches来完成的。

也就是说上面那整棵调用树所做的事情就是检查容器知否已存在RequestDataValueProcessor类型的bean,如果存在则不再重复重新加载这个RequestDataValueProcessor,如果是普通的bean那还好办直接比较bean的类型是否是RequestDataValueProcessor类型就行了,不必去初始化整个bean,但是如果是FactoryBean那就坏事了,因为FactoryBean类型的bean最终创建的bean的类型并不是FactoryBean本身的类型,而是由它的getObject返回值来决定的,所以要拿到bean类型,会调它的getObject方法创建bean之后再比较类型。

而dubbo消费者bean的类型都是com.alibaba.dubbo.config.spring.ReferenceBean,这恰恰是一个FactoryBean,此时会初始化消费者bean,设置group属性时接着初始化引用的的disconfPropertiesReader bean,所以就导致了悲剧。

org.springframework.beans.factory.support.AbstractBeanFactory.isTypeMatch

最后只剩下最后一个问题,WebMvcSecurityConfiguration是从哪来的,搜索代码,直接宣布结果:

由于工程使用了AutoConfiguration特性,框架查找并读取所有名称是*AutoConfiguration的类,所以会找到SecurityAutoConfiguration。

这个类定义了@Configuration注解,所以会加载这个类中定义的bean,发现了@Import注解,根据这个标签读取到并解析SpringBootWebSecurityConfiguration,继续读取到它的内部类WebMvcSecurityConfigurationConditions,再读取到内部类的内部类DefaultWebMvcSecurityConfiguration,这个内部类上有@EnableWebMvcSecurity注解,这个注解又@Import了WebMvcSecurityConfiguration,这个类有@EnableWebSecurity注解,注解@Import了SpringWebMvcImportSelector,这个ImportSelector会导入org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration:

总结产生问题的最终原因:

是因为工程使用了Spring Boot的Auto Configuration功能,而根据这个工程的特点会自动加载安全配置SecurityAutoConfiguration,由此经过一系列的直接或间接的import,会导致框架读取WebMvcSecurityConfiguration,而这个配置在加载RequestDataValueProcessor这个bean定义的时候由于有@ConditionalOnMissingBean的作用导致框架会检查容器中是否已经存在了RequestDataValueProcessor类型的bean实例,在检查的过程中当扫描到sendCouponRemoteApiImpl这个dubbo消费者bean时,由于它是一个FactoryBean,getObject方法会被调用进而执行了bean的初始化动作,而由于此时由于处理属性占位符的PropertySourcesPlaceholderConfigurer还未被激活,所以导致bean的属性值没有被正确初始化。

解决方案

治标的方案,考虑到工程主要是提供dubbo服务无需启用安全配置,可以禁调安全配置的自动加载,@EnableAutoConfiguration注解有exclude属性,通过它可以禁用对SecurityAutoConfiguration的自动加载:

治本的方案,spring社区也有人提出了相同的问题:https://jira.spring.io/browse/SEC-3063,已确定是spring的bug,官方已在spring-security-config的4.1.0版本解决了这个问题,删掉了

ConditionalOnMissingBean这个注解,看代码WebMvcSecurityConfiguration也不再使用这个注解了,升级spring-security-config到4.1.0解决问题:

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • java判断Long类型的方法和实例代码

    java判断Long类型的方法和实例代码

    在本篇文章里小编给大家整理的是关于java判断Long类型的方法和实例代码,对此有需要的朋友们跟着学习参考下。
    2020-02-02
  • Java的特点和优点(动力节点整理)

    Java的特点和优点(动力节点整理)

    由于Java语言的设计者们十分熟悉C++语言,所以在设计时很好地借鉴了C++语言。可以说,Java语言是一种比C++语言“还面向对象”的一种编程语言,下面通过本文说下java的特点和优点
    2017-03-03
  • SpringCache的基本使用方法

    SpringCache的基本使用方法

    Spring Cache利用了AOP,实现了基于注解的缓存功能,并且进行了合理的抽象,业务代码不用关心底层是使用了什么缓存框架,只需要简单地加一个注解,就能实现缓存功能了,本文介绍SpringCache的基本使用方法,感兴趣的朋友一起看看吧
    2024-01-01
  • java实现三角形分形山脉

    java实现三角形分形山脉

    这篇文章主要为大家详细介绍了java实现三角形分形山脉,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01
  • Kafka源码系列教程之删除topic

    Kafka源码系列教程之删除topic

    这篇文章主要给大家介绍了关于Kafka源码系列教程之删除topic的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-08-08
  • SpringBoot扩展外部化配置的原理解析

    SpringBoot扩展外部化配置的原理解析

    这篇文章主要介绍了SpringBoot扩展外部化配置的原理解析,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-03-03
  • 如何使用Java操作Zookeeper

    如何使用Java操作Zookeeper

    这篇文章主要介绍了如何使用Java操作Zookeeper,帮助大家更好的理解和学习使用Java,感兴趣的朋友可以了解下
    2021-04-04
  • Springboot 内部服务调用方式

    Springboot 内部服务调用方式

    这篇文章主要介绍了Springboot 内部服务调用方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • springboot controller无效的处理方案

    springboot controller无效的处理方案

    这篇文章主要介绍了springboot controller无效的处理方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • MyBatis Plus 实现多表分页查询功能的示例代码

    MyBatis Plus 实现多表分页查询功能的示例代码

    这篇文章主要介绍了MyBatis Plus 实现多表分页查询功能,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08

最新评论