【问题标题】:Referring to ConfigurationProperties Beans in SpEL expression in @Configuration bean在 @Configuration bean 中引用 SpEL 表达式中的 ConfigurationProperties bean
【发布时间】:2015-10-08 15:34:17
【问题描述】:

我有这个属性类:

import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("some")
    public class SomeProperties {
        private List<String> stuff;

        public List<String> getStuff() {
            return stuff;
        }

        public void setStuff(List<String> stuff) {
            this.stuff = stuff;
        }    
    }

我在 @Configuration 类中启用配置属性,如下所示:

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(SomeProperties.class)
public class SomeAutoConfiguration {    
}

在同一个类(“SomeAutoConfiguration”)中,我想根据 SomeProperties 中的列表属性是否为空来创建另一个 bean。我想我可以将@ConditionalExpression 与以下 SpEl 一起使用:

@Bean
@ConditionalOnExpression("!(#someProperties.stuff?.isEmpty()?:true)")   
    public Object someBean(final SomeProperties someProperties) {
    return new Object();
}    

SpEl 是正确的,但我无法控制包含我的属性的 bean。使用上面的表达式我遇到了

EL1007E:(pos 43): 找不到属性或字段“stuff” 空

并试图通过它的名字来获取 bean,比如

@Bean
@ConditionalOnExpression("!(@'some.CONFIGURATION_PROPERTIES'.stuff?.isEmpty()?:true)")  
    public Object someBean(final SomeProperties someProperties) {
    return new Object();
} 

结束

NoSuchBeanDefinitionException:没有定义名为“some.CONFIGURATION_PROPERTIES”的bean

有什么想法吗?我已经尝试在另一个类中启用 ConfigurationProperties,但这也没有用。

【问题讨论】:

    标签: java spring spring-boot spring-el


    【解决方案1】:

    基于Brice's answer我做了一个抽象条件:

    public abstract class ConfigurationPropertyCondition<T> extends SpringBootCondition {
        private final Class<T> propertiesClass;
        private final Supplier<T> constructor;
        private final String prefix;
        private final Predicate<T> predicate;
    
        public ConfigurationPropertyCondition(
                Class<T> propertiesClass, Supplier<T> constructor, String prefix, Predicate<T> predicate) {
            this.propertiesClass = propertiesClass;
            this.constructor = constructor;
            this.prefix = prefix;
            this.predicate = predicate;
        }
    
        @Override
        public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
            return new ConditionOutcome(predicate.test(properties(context)), (String) null);
        }
    
        private T properties(ConditionContext context) {
            return Binder.get(context.getEnvironment())
                    .bind(ConfigurationPropertyName.of(prefix), Bindable.of(propertiesClass))
                    .orElseGet(constructor);
        }
    }
    

    现在我可以做类似的事情了:

    ...
    @ConfigurationProperties(prefix = MY_FEATURE_SETTINGS)
    @Bean
    public MyFeatureProperties myFeatureProperties() {
        return new MyFeatureProperties();
    }
    ...
    
    @Conditional(MyFeatureConfiguration.MyFeatureEnabled.class)
    @Configuration
    public class MyFeatureConfiguration {
        ...
    
        static class MyFeatureEnabled extends ConfigurationPropertyCondition<MyFeatureProperties> {
            public MyFeatureEnabled() {
                super(MyFeatureProperties.class, MyFeatureProperties::new, MY_FEATURE_SETTINGS, MyFeatureProperties::isEnabled);
            }
        }
    }
    
    

    【讨论】:

      【解决方案2】:

      为了补充 @PhilWebb 关于 Spring Boot 2+ 的回答,RelaxedPropertyResolver 已被删除,取而代之的是更强大的替代方案,名为 Binder。这是一个非常简单的例子:

      @Configuration
      @AutoConfigureBefore(JacksonAutoConfiguration.class)
      @ConditionalOnMissingBean(ObjectMapper.class)
      @Conditional(SpringJacksonPropertiesMissing.class)
      public class ObjectMapperConfiguration {
          @Bean
          public ObjectMapper objectMapper() {
              return new ObjectMapper()
                      .disable(FAIL_ON_UNKNOWN_PROPERTIES)
                      .setSerializationInclusion(JsonInclude.Include.NON_NULL);
          }
      
          static class SpringJacksonPropertiesMissing extends SpringBootCondition {
              @Override
              public ConditionOutcome getMatchOutcome(ConditionContext context,
                                                      AnnotatedTypeMetadata metadata) {
                  return new ConditionOutcome(hasJacksonPropertiesDefined(context),
                                              "Spring Jackson property based configuration missing");
              }
      
              private boolean hasJackonPropertiesDefined(ConditionContext context) {
                  return Binder.get(context.getEnvironment())
                               .bind(ConfigurationPropertyName.of("spring.jackson"),
                                                      Bindable.of(Map.class))
                               .orElse(Collections.emptyMap())
                               .isEmpty();
              }
          }
      }
      

      免责声明:此代码用于逐步淘汰有关杰克逊对象映射器的一些不良做法,以便将某些代码转换为spring boot way to configure an object mapper

      【讨论】:

        【解决方案3】:

        我认为您面临的问题是 @Conditions 在解析 @Configuration 类时被评估,因此不能保证 SomeProperties bean 已被定义。即使已定义,您也可能不希望过早对其进行初始化,因此我建议采用不同的方法。

        您可以尝试@ConditionalOnPropety,这是 Spring Boot 在有条件地想要基于属性启用自动配置时在内部使用的注释。如果这不够灵活,您可以创建自己的Condition 并直接访问Environment 以判断属性值是否为空。如果你想支持灵活绑定,你可以使用RelaxedPropertyResolverHere's an example.

        【讨论】:

        • 谢谢菲尔。我认为这正是我害怕或怀疑的。当我尝试编写一个 spring-boot-starter 时,我不会编写自定义注释。我想要实现的差异化可以用普通的旧 Java 代码编写。无论如何,谢谢,对机制的更多了解是更好的理解:)
        • 您不需要编写自定义注解来拥有自定义条件,您可以创建一个SpringBootCondition 类并从@Conditional 使用它。 Here's one example 来自 Spring Boot 内部。
        • @PhilWebb 我创建了一个自定义条件,但是当我调用/actuator/refresh 时,该条件在运行时没有重新评估。我有@Bean 和包含@Configuration 的类用@RefreshScope 注释。可能是什么问题
        猜你喜欢
        • 1970-01-01
        • 2020-10-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-09-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多