#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")