springboot 关于@ConditionalOnExpression注解,在使用spel表达式引用配置属性bean导致提前初始化,无绑定数据的问题及相应的解决方法。
SpringBoot版本
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.7</version>
</parent>
原始需求
一开始我的需求就是想通过yml配置文件里的数组结构的值,从而判断指定bean是否进行注册到IOC容器中。
不过后来通过观察Environment bean
的注入结果,发现里面的属性都被扁平化了。
例如:
students:
groups:
- xiaoming
- xiaowang
在Environment里的体现为:
students.groups[0]=xiaoming
students.groups[1]=xiaowang
后来想通过@ConfigurationProperties配置bean,将yml文件的数组结构映射到下边的配置bean。
public class Student{
private List<Student> groups;
// ...get ...set
}
最后,值是映射到groups里了,到这一步是ok的。
然后通过@ConditionalOnExpression注解使用spel,结果导致 配置bean提前初始化,状态不完整,所以groups属性等于null。
通过看官方springboot文档,对其解释引用的bean会被提前初始化,导致状态不完整
最后也是只能作罢。
最后只能自己实现了。没招了。
问题原因
在org.springframework.boot.autoconfigure.condition.ConditionalOnExpression
注解的注释说明了,在spel引用的bean会提前初始化,导致bean的状态不完整导致的。
@ConditionalOnExpression注解虽然使用可以使用spel引用bean,但是我觉的还是适用到那些标记Bean
或通过配置值进行判断的情况。
如果在#{ @xxxBean.property }
的话,那这时候xxxBean下的属性可能会为null,也就是配置文件的属性值没绑定到bean中。
关于@ConditionalOnExpression注解的讨论
Github 注释方法时属性值未正确加载@ConditionalOnExpression
解决方法
第一种,直接使用@ConditionalOnExpression注解的spel表达式不引用配置bean了,通过获取配置文件的属性直接进行条件的限定。
// 以下表达式就是当 值 在数组里包含,则返回true,否则返回false。经测试,可行。
@ConditionalOnExpression(value = "#{ T(org.springframework.boot.context.properties.bind.Binder).get(environment).bind('yml配置里的数组结构的完整Key', T(org.springframework.boot.context.properties.bind.Bindable).listOf(T(String))).get().contains('你想包含的数组值') }")
第二种就是自己将以上的逻辑包装为注解。
实现逻辑类需要继承org.springframework.boot.autoconfigure.condition.SpringBootCondition
类,方便实现,里面加入了共用的逻辑,你只需要依葫芦画瓢就行了。
然后在你的自定义注解上加上org.springframework.context.annotation.Conditional
注解,value=你自己实现的条件类,这样就可以了。
最后使用,只需要放在你需要进行条件限定的类,并加上属性和值的指定,基本就ok了。
参阅资源
SpEL expressions-types
SpEL Expression Conditions
stackoverflow conditionalonexpression