【问题标题】:Conditionally create bean if Map of properties is not empty with SpEL如果使用 SpEL 的属性映射不为空,则有条件地创建 bean
【发布时间】:2019-04-10 11:39:40
【问题描述】:

我有一个类,该类带有一个使用@Scheduled 注释定期调用的方法。该方法对给定的一组属性执行一些批量操作。 如果没有设置属性,我不需要计划的方法调用,也不需要实例化的类。因此,我添加了这个SpEL 表达式来检查是否设置了属性:

@Service
@ConditionalOnExpression("#(T(java.util.Map)('${myproperties.people:{:}}')).size() > 0")
public class PeopleService { ... }

application.yml 中的示例值可能是:

myproperties:
  people:
    uuid1:
      name: Mark
      age: 32
    uuid2:
      name: Jeff
      age: 36

很遗憾,我收到此错误消息:

Caused by: org.springframework.expression.spel.SpelParseException: Expression [#(T(java.util.Map)('{:}')).size() > 0] @1: EL1043E: Unexpected token. Expected 'identifier' but was 'lparen(()'

请注意,我在这里将{:} 用作默认值:https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#expressions-inline-maps

如果我使用这个 SpEL,我会收到以下错误:"#(T(java.util.Map)(${myproperties.people:})).size() > 0"

Caused by: org.springframework.expression.spel.SpelParseException: Expression [#(T(java.util.Map)()).size() > 0] @1: EL1043E: Unexpected token. Expected 'identifier' but was 'lparen(()'

实现此目的的正确方法是什么?

【问题讨论】:

    标签: spring spring-boot spring-el


    【解决方案1】:

    这个注释:

    @ConditionalOnExpression("#(T(java.util.Map)('${myproperties.people:{:}}')).size() > 0")
    

    不起作用,因为 Spring 会像这样读取您的所有属性:

    • myproperties.people.uuid1.name=马克
    • myproperties.people.uuid1.age=32
    • myproperties.people.uuid2.name=杰夫
    • myproperties.people.uuid2.age=36

    如果您查看 Spring Boot 文档,this page 解释说“如果环境中存在 spring.example.values,则条件匹配,但如果 spring.example.values[0] 存在,则条件不匹配。”。

    所以对于 Spring,属性“myproperties.people”根本不存在。在同一页上,他们解释说“最好对这种情况使用自定义条件。”

    一种解决方法是定义一个属性类(但不会浪费,因为您将使用该类将您的 yaml 解析为地图):

    @ConfigurationProperties(prefix = "myproperties")
    public class MyPeopleProperties {
        
        private Map<String, String> people;
    
        public Map<String, String> getPeople() {
            return people;
        }
    
        public void setPeople(Map<String, String> people) {
            this.people = people;
        }
    
    }
    

    在你的主课上:

    ...
    import org.springframework.context.annotation.Condition;
    import org.springframework.context.annotation.ConditionContext;
    import org.springframework.context.annotation.Conditional;
    ...
    
    @Service
    @Conditional(PeopleService.PeopleServiceCondition.class)
    public class PeopleService { 
    
        ... 
    
        public static class PeopleServiceCondition implements Condition {
    
            @Override
            public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
                MyPeopleProperties config = Binder.get(context.getEnvironment())
                        .bind("myproperties", MyPeopleProperties.class).orElse(null);
                return config != null && config.getPeople() != null && !config.getPeople().isEmpty();
            }
    
        }
    
    }
    

    所以myproperties.people会被绑定为map,然后我们在bean声明条件下检查这个map是否存在。

    【讨论】:

      【解决方案2】:

      回到 SpEL 实现,您实际上可以完全在 SpEL 中完成它,但它真的很难看:

      @ConditionalOnExpression(
          "(" +
              "T(org.springframework.boot.context.properties.bind.Binder)" +
              ".get(environment)" +
              ".bind('myproperties.people', T(java.util.Map))" +
              ".orElse(null)" +
              "?.size()" +
              "?: 0" +
          ") > 0"
      )
      

      这甚至适用于深度嵌套的配置子树 - 与 Map 类的简单绑定只会创建一堆非规范化/扁平化的 &lt;String,String&gt; 条目,其中包含 uuid1.name 之类的键。这意味着,如果您只是测试 myproperties.people 命名空间下是否存在 any 子树元素,则上述方法可以正常工作。实际上,这正是您在写 @ConditionalOnProperty("myproperties.people") 时可能的意思

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-08-10
        • 2022-01-07
        • 1970-01-01
        • 2018-04-10
        • 2021-09-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多