java @ConditionalOnProperty用于列表还是数组?

ujv3wf0j  于 2023-09-29  发布在  Java
关注(0)|答案(5)|浏览(206)

我使用的是Sping Boot 1.4.3 @AutoConfiguration,我根据用户指定的属性自动创建bean。用户可以指定服务数组,其中名称版本为必填字段:

service[0].name=myServiceA
service[0].version=1.0

service[1].name=myServiceB
service[1].version=1.2

...

如果用户忘记在一个服务上指定一个必需的字段,那么我希望后退,不创建任何bean。我可以用@ConditionalOnProperty来实现吗?我想要的是:

@Configuration
@ConditionalOnProperty({"service[i].name", "service[i].version"})
class AutoConfigureServices {
....
}
scyqe7ek

scyqe7ek1#

这是我创建的自定义Condition。它需要一些抛光更通用(即不硬编码字符串),但对我来说工作得很好。
为了使用,我用@Conditional(RequiredRepeatablePropertiesCondition.class)注解了Configuration类

public class RequiredRepeatablePropertiesCondition extends SpringBootCondition {

    private static final Logger LOGGER = LoggerFactory.getLogger(RequiredRepeatablePropertiesCondition.class.getName());

    public static final String[] REQUIRED_KEYS = {
            "my.services[i].version",
            "my.services[i].name"
    };

    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        List<String> missingProperties = new ArrayList<>();
        RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(context.getEnvironment());
        Map<String, Object> services = resolver.getSubProperties("my.services");
        if (services.size() == 0) {
            missingProperties.addAll(Arrays.asList(REQUIRED_KEYS));
            return getConditionOutcome(missingProperties);
        }
        //gather indexes to check: [0], [1], [3], etc
        Pattern p = Pattern.compile("\\[(\\d+)\\]");
        Set<String> uniqueIndexes = new HashSet<String>();
        for (String key : services.keySet()) {
            Matcher m = p.matcher(key);
            if (m.find()) {
                uniqueIndexes.add(m.group(1));
            }
        }
        //loop each index and check required props
        uniqueIndexes.forEach(index -> {
            for (String genericKey : REQUIRED_KEYS) {
                String multiServiceKey = genericKey.replace("[i]", "[" + index + "]");
                if (!resolver.containsProperty(multiServiceKey)) {
                    missingProperties.add(multiServiceKey);
                }
            }
        });
        return getConditionOutcome(missingProperties);
    }

    private ConditionOutcome getConditionOutcome(List<String> missingProperties) {
        if (missingProperties.isEmpty()) {
            return ConditionOutcome.match(ConditionMessage.forCondition(RequiredRepeatablePropertiesCondition.class.getCanonicalName())
                    .found("property", "properties")
                    .items(Arrays.asList(REQUIRED_KEYS)));
        }
        return ConditionOutcome.noMatch(
                ConditionMessage.forCondition(RequiredRepeatablePropertiesCondition.class.getCanonicalName())
            .didNotFind("property", "properties")
            .items(missingProperties)
        );
    }
}
7jmck4yq

7jmck4yq2#

您可以利用org.springframework.boot.autoconfigure.condition.OnPropertyListCondition类。例如,如果你想检查service属性是否至少有一个值:

class MyListCondition extends OnPropertyListCondition {
    MyListCondition() {
        super("service", () -> ConditionMessage.forCondition("service"));
    }
}
@Configuration
@Condition(MyListCondition.class)
class AutoConfigureServices {

}

请参阅org.springframework. Boot .autoconfigure.webservices.WebServicesAutoConfiguration#wsdlDefinitionBeanFactoryPostProcessor上使用的org.springframework.boot.autoconfigure.webservices.OnWsdlLocationsCondition,以获取Spring本身的示例。

xxe27gdn

xxe27gdn3#

老问题了,但我希望我的回答能对Spring2.x有所帮助:感谢@Brian,我查看了迁移指南,在那里我受到了示例代码的启发。这段代码对我很有效:

final List<String> services = Binder.get(context.getEnvironment()).bind("my.services", List.class).orElse(null);

我确实尝试获取POJO的列表(作为AutoConfigureService),但我的类与AutoConfigureServices不同。为此,我使用了:

final Services services = Binder.get(context.getEnvironment()).bind("my.services", Services.class).orElse(null);

继续玩:D

ki1q1bka

ki1q1bka4#

下面是我对这个问题的看法,在Spring自动配置中使用自定义条件。有点类似于@Strumbels提出的,但更可重用。
@Conditional注解在应用程序启动的早期执行。属性源已加载,但尚未创建ConfgurationProperties bean。但是,我们可以通过将属性绑定到JavaPOJO来解决这个问题。
首先,我介绍一个功能接口,这将使我们能够定义任何自定义逻辑检查,如果属性实际上存在与否。在你的例子中,这个方法将负责检查属性List是否为空/null,以及其中的所有项是否有效。

public interface OptionalProperties {
  boolean isPresent();
}

现在让我们创建一个annotation,它将使用Spring @Conditional进行metannotated,并允许我们定义自定义参数。prefix表示属性名称空间,targetClass表示属性应Map到的配置属性模型类。

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

  String prefix();

  Class<? extends OptionalProperties> targetClass();

}

现在是主要部分。自定义条件实现。

public class OnConfigurationPropertiesCondition extends SpringBootCondition {

  @Override
  public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
    MergedAnnotation<ConditionalOnConfigurationProperties> mergedAnnotation = metadata.getAnnotations().get(ConditionalOnConfigurationProperties.class);
    String prefix = mergedAnnotation.getString("prefix");
    Class<?> targetClass = mergedAnnotation.getClass("targetClass");
    // type precondition
    if (!OptionalProperties.class.isAssignableFrom(targetClass)) {
      return ConditionOutcome.noMatch("Target type does not implement the OptionalProperties interface.");
    }
    // the crux of this solution, binding properties to Java POJO
    Object bean = Binder.get(context.getEnvironment()).bind(prefix, targetClass).orElse(null);
    // if properties are not present at all return no match
    if (bean == null) {
      return ConditionOutcome.noMatch("Binding properties to target type resulted in null value.");
    }
    OptionalProperties props = (OptionalProperties) bean;

    // execute method from OptionalProperties interface 
    // to check if condition should be matched or not
    // can include any custom logic using property values in a type safe manner
    if (props.isPresent()) {
      return ConditionOutcome.match();
    } else {
      return ConditionOutcome.noMatch("Properties are not present.");
    }
  }

}

现在,您应该创建自己的配置属性类,实现OptionalProperties接口。

@ConfigurationProperties("your.property.prefix")
@ConstructorBinding
public class YourConfigurationProperties implements OptionalProperties {

  // Service is your POJO representing the name and version subproperties
  private final List<Service> services;

  @Override
  public boolean isPresent() {
    return services != null && services.stream().all(Service::isValid);
  }

}

然后在Spring @Configuration类中。

@Configuration
@ConditionalOnConfigurationProperties(prefix = "", targetClass = YourConfigurationProperties.class)
class AutoConfigureServices {
....
}

这种解决方案有两个缺点:

  • 必须在两个位置指定属性前缀:在@ConfigurationProperties注解上和在@ConditionalOnConfigurationProperties注解上。通过在配置属性POJO中定义一个public static final String PREFIX = "namespace",可以部分缓解这种情况。
  • 每次使用我们的自定义条件注解时,都单独执行属性绑定过程,然后再次创建配置属性bean本身。它只发生在应用程序启动期间,所以它不应该是一个问题,但它仍然是一个低效率。
lhcgjxsq

lhcgjxsq5#

据我所知,你的问题是如何验证必填字段,我的建议是使用@ConfigurationProperties(“root”)注解,然后像这样将所有字段添加为@NotNull:

@Getter
@Validated
@RequiredArgsConstructor
@ConfigurationProperties("root")
public class YourProperties {

  private final Set<Item> service;

  @Getter
  @Validated
  @RequiredArgsConstructor
  public static class Item {

    @NotNull
    private final String name;

    @NotNull
    private final String version;
  }
}

如果你更喜欢继续使用条件方法,你可以使用ConditionalOnExpression,但是,应该注意项目的数量是无限的:

@ConditionalOnExpression("#{T(org.springframework.util.StringUtils).hasText('${service[0].name}')}")

相关问题