Using Spring expression language to assert if a property (with comma separated values) loaded from a properties file, contains "value"

huangapple go评论91阅读模式
英文:

Using Spring expression language to assert if a property (with comma separated values) loaded from a properties file, contains "value"

问题

我有以下问题,我在Spring Boot中有一个特定的配置类,其中包含一些Bean,我希望只有在某个属性满足一定条件时才创建这些Bean。该属性是一个包含一组值的列表。

现在配置类大致如下所示:

@ConditionalOnExpression("#{'${conditions.values.options}'.contains('ScenarioOne')}")
ConfigurationForScenarioOne {

  @Bean
  public StudentBean getStudentBean(){
  ...
  }
}

在属性文件中,我会有类似以下的内容:

conditions.values.options=${CONDITIONS_VALUES_OPTIONS}

然后在运行时提供CONDITIONS_VALUES_OPTIONS的值,例如:

-DCONDITIONS_VALUES_OPTIONS=ScenarioOne,ScenarioTwo,ScenarioFive

我之所以这样做,是为了能够部署应用程序,并根据运行时提供的值使用不同的Bean。我将会有多个这样的配置类,根据传递的属性来确定创建哪些配置类。

为了实现这一点,我试图依赖于Spring表达式语言来执行以下操作:

  1. 读取属性conditions.values.options
  2. 将其转换为列表,
  3. 验证列表是否包含所需的配置字符串。

到目前为止,我尝试了许多表达式,包括:

@ConditionalOnExpression("#{'${conditions.values.options}'.contains('ScenarioOne')}")

以及其他类似的表达式,但都没有成功。有人可以帮助我看看我在这里漏掉了什么吗?

英文:

I have the following problem, I have a certain configuration class in spring boot which contains beans, which I would like to be created only if a certain property which has a list of values, contains a certain value.

now the configuration class looks something like this:

@ConditionalOnExpression("#{'${conditions.values.options}'.contains('ScenarioOne')}")
ConfigurationForScenarioOne{

  @Bean
  public StudentBean getStudentBean(){
  ...
  }
}

In the properties file I would have something like so:

conditions.values.options=${CONDITIONS_VALUES_OPTIONS}

Then provide the value of CONDITIONS_VALUES_OPTIONS at runtime like so:

-DCONDITIONS_VALUES_OPTIONS=ScenarioOne,ScenarioTwo,ScenarioFive

The reason why I want to do this is to be able to deploy the app and have different beans be in use depending on the value given at runtime.

I will have several of these Configuration classes that will be created based on which ones of these properties are passed.
TO achieve this I was trying to rely on Spring Expression language to do the following:
1-Read the property conditions.values.options, then convert it to a list, then verify if the list contained the desired configuration string.

So far I have tried several expressions including:

@ConditionalOnExpression("#{'${conditions.values.options}'.contains('ScenarioOne')}")

and other similar expressions, with no luck. Can someone help me see what I am missing here?

答案1

得分: 3

我能够通过以下方式实现所需的行为:

@ConditionalOnExpression("#{T(java.util.Arrays).asList('${conditions.values.options}').contains('ScenarioOne')}")

因此,稍微解析一下,希望能够帮助那些可能遇到类似问题的人:
似乎 Spring 将逗号分隔的属性读取为数组,这是错误的,因为可以使用 Spring Boot 来完成以下操作:

以下两个语句都是有效的:

@Value("${property.with.comma.separated.values}")
private List<String> prop;

或者

@Value("${property.with.comma.separated.values}")
private String[] prop;

现在是表达式:

"${conditions.values.options}".contains("ScenarioOne")

会抛出错误,说 Array 类没有名为 .contains() 的方法,因此我不得不采取使用一些方法调用:

T(java.util.Arrays).asList('${conditions.values.options}')

来读取逗号分隔的值,将字符串数组转换为 List<String>,然后执行 .contains(..) 方法。

我希望有一种更简单的方法来做这件事,必须在其中进行方法调用似乎有点过度设计。

我愿意听取建议 😊。

英文:

I was able to get the desired behavior using this:

@ConditionalOnExpression(&quot;#{T(java.util.Arrays).asList(&#39;${conditions.values.options}&#39;).contains(&#39;ScenarioOne&#39;)}&quot;)

So dissecting this a little bit in the hopes to help others who may come across similar problems:
It seems that spring reads comma separated properties as an Array, this is bogus because the following can be done using spring boot:

The following two statements are valid:

@Value(&quot;${property.with.comma.separated.values}&quot;)
private List&lt;String&gt; prop;

OR

@Value(&quot;${property.with.comma.separated.values}&quot;)
private String [] prop;

now the expression:

&quot;#{&#39;${conditions.values.options}&#39;.contains(&#39;ScenarioOne&#39;)}&quot;

will throw an error saying that the Array class does not contain a method called '.contains()'
hence this is why I had to resort to use some method execution:

T(java.util.Arrays).asList(&#39;${conditions.values.options}&#39;)

to read the comma separated values, convert the String array into a List<String> and then perform the '.contains(..)' method invocation.

I wish there was a simpler way to do it, to have to do a method invocation in there seems like overkill.

I am open to sugestions 😀

答案2

得分: 0

一个替代方法是实现一个自定义的条件注解。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnHavingValueCondition.class)
public @interface ConditionalOnHavingValue {

    /**
     * 应该包含该值的属性。
     */
    String property();

    /**
     * 必须存在的值。
     */
    String value();

}
public class OnHavingValueCondition extends SpringBootCondition {

    @Override
    public ConditionOutcome getMatchOutcome(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
        final MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(ConditionalOnHavingValue.class.getName());
        if (attributes == null) {
            return ConditionOutcome.noMatch("无法从 ConditionalOnHavingValue 注解中获取属性");
        }

        final String property = (String) attributes.getFirst("property");
        final String value = (String) attributes.getFirst("value");

        //noinspection unchecked,DataFlowIssue
        final Set<String> propertyValues = (Set<String>) context.getEnvironment().getProperty(property, Set.class);

        //noinspection DataFlowIssue
        if (!propertyValues.contains(value)) {
            return ConditionOutcome.noMatch("在属性中找不到特定的值");
        }

        return ConditionOutcome.match();
    }

}
英文:

An alternative is to implement a custom conditional annotation.

@ConditionalOnHavingValue(property = &quot;conditions.values.options&quot;, value = &quot;ScenarioOne&quot;)

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnHavingValueCondition.class)
public @interface ConditionalOnHavingValue {

    /**
     * The property that should contain the value.
     */
    String property();

    /**
     * The value that must be present.
     */
    String value();

}
public class OnHavingValueCondition extends SpringBootCondition {

    @Override
    public ConditionOutcome getMatchOutcome(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
        final MultiValueMap&lt;String, Object&gt; attributes = metadata.getAllAnnotationAttributes(ConditionalOnHavingValue.class.getName());
        if (attributes == null) {
            return ConditionOutcome.noMatch(&quot;Unable to retrieve attributes from ConditionalOnHavingValue annotation&quot;);
        }

        final String property = (String) attributes.getFirst(&quot;property&quot;);
        final String value = (String) attributes.getFirst(&quot;value&quot;);

        //noinspection unchecked,DataFlowIssue
        final Set&lt;String&gt; propertyValues = (Set&lt;String&gt;) context.getEnvironment().getProperty(property, Set.class);

        //noinspection DataFlowIssue
        if (!propertyValues.contains(value)) {
            return ConditionOutcome.noMatch(&quot;Unable to find the particular value in the property&quot;);
        }

        return ConditionOutcome.match();
    }

}

答案3

得分: -1

请尝试将您的注解值以以下方式编写并检查:

@ConditionalOnExpression("#{${conditions.values.options}}.contains('ScenarioOne')")

在大括号后面写入contains方法。

英文:

Can you try writing your annotation values like this and check :

@ConditionalOnExpression(&quot;#{${conditions.values.options}}.contains(&#39;ScenarioOne&#39;)&quot;)

Write the contains method after the curly brackets.

huangapple
  • 本文由 发表于 2020年4月7日 02:25:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/61066447.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定