Условно создать компонент, если карта свойств не пуста с помощью SpEL

#spring #spring-boot #spring-el

#spring #spring-boot #spring-el

Вопрос:

У меня есть класс с методом, который периодически вызывается с @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(()'
  

Каков правильный способ добиться этого?

Ответ №1:

Эта аннотация:

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

Не работает, потому что Spring считывает БУКВАЛЬНО все ваши свойства таким образом:

  • myproperties.people.uuid1.name=Отметить
  • myproperties.people.uuid1.age=32
  • myproperties.people.uuid2.name=Jeff
  • myproperties.people.uuid2.age=36

Если вы проверяете документацию по загрузке Spring, на этой странице объясняется, что «условие соответствует, если 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 amp;amp; config.getPeople() != null amp;amp; !config.getPeople().isEmpty();
        }

    }

}
  

So myproperties.people будет привязан как 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 классу просто создает кучу денормализованных / сглаженных <String,String> записей с такими ключами, как uuid1.name . Это означает, что если вы просто проверяете наличие каких-либо элементов поддерева в myproperties.people пространстве имен, вышеуказанное будет работать нормально. По сути, это именно то, что вы, вероятно, имели в виду при написании @ConditionalOnProperty("myproperties.people")