#spring-boot #spring-validator #spring-properties
Вопрос:
Я использую spring-boot-starter-parent
версию 2.4.4
.
Я экспериментирую с проверкой типизированной конфигурации с использованием @ConstructorBinding
и без.
Вот типизированная конфигурация, которая проверяется и правильно останавливает запуск приложения. Это не использование @ConstructorBinding
, а стиль геттера/сеттера:
@ConfigurationProperties("app")
@Validated
@Getter
@Setter
public static class AppProperties implements org.springframework.validation.Validator {
private int someProp = 10;
@Override
public boolean supports(Class<?> aClass) {
return aClass.isAssignableFrom(getClass());
}
@Override
public void validate(Object o, Errors errors) {
AppProperties appProperties = (AppProperties) o;
if (appProperties.getSomeProp() < 100) {
errors.rejectValue("someProp", "code", "Number should be greater than 100!");
}
}
}
Веб-приложение настроено @ConfigurationPropertiesScan
так, чтобы все работало. При запуске, как и ожидалось, я получаю это:
Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'app' to com.example.demo.DemoApplication$AppProperties failed:
Property: app.someProp
Value: 10
Reason: Number should be greater than 100!
Здорово.
Но я фанат инъекции конструктора, и я хотел переработать это в использование @ConstructorBinding
. Давайте сделаем это:
@ConfigurationProperties("app")
@ConstructorBinding // !!!
@Validated
@Getter
// @Setter - no setters!
public static class AppProperties implements org.springframework.validation.Validator {
private int someProp = 10;
// constructor!
public AppProperties(int someProp) {
this.someProp = someProp;
}
// same validation as above
}
This time, the app starts. No errors. I was expecting the app NOT to run, and for me to get the same startup error, because the injected someProp
is 0
(I haven’t declared it anywhere), so clearly invalid according to my validation. If I debug the AppProperties
, the someProp
member is indeed 0
.
I did some research, but I’m too big a Spring noob to figure out why. Here’s some of my findings:
- although I agree with the Spring devs that a
org.springframework.validation.Validator
should be a standalone object, meant to validate many objects, the matter of fact is that aValidator
is considered when binding to a Typed Config implementingValidator
. See here: GitHub link. That code says «If your class implements Validator, I’m going to use a special binder to when creating your bean, which will validate it». - using the getter/setter style, you get two validators from
org.springframework.boot.context.properties.ConfigurationPropertiesBinder#getValidators()
— JSR-303 andValidator
, while using@ConstructorBinding
gets you one — only JSR-303. Why is that? Well, because the target — my Typed Config — has no value yet when using the constructor binding: see here the condition. - related to the above, on ctor binding, this condition is false, thus bypassed: condition bypassed.
- depending on strategy, the bean definitely gets created differently:
Это, вероятно, связано с временем создания компонента- ctor рано, сеттер позже. Тем не менее, на данный момент это просто любопытство.
Любые идеи, как я могу сделать типизированную конфигурацию, привязанную к конструктору, для проверки с помощью Validator
? Как бы вы поступили вместо этого, если бы это был не тот путь?
Спасибо, что прочитали.