Skip to content

Commit df1d1db

Browse files
committed
Fallback to BeanPropertyBindingResult for unbound property validation
gh-17424 updated `ValidationBindHandler` so that it would only look at bound values when validating. This commit updates `ValidationBindHandler` to use Spring Framework's `BeanPropertyBindingResult`. This means that for fields that are not bound, JavaBean accessor methods can be used to get the value to validate. Fixes gh-25356
1 parent df97627 commit df1d1db

File tree

4 files changed

+88
-9
lines changed

4 files changed

+88
-9
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/validation/ValidationBindHandler.java

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.Set;
2323
import java.util.stream.Collectors;
2424

25+
import org.springframework.beans.NotReadablePropertyException;
2526
import org.springframework.boot.context.properties.bind.AbstractBindHandler;
2627
import org.springframework.boot.context.properties.bind.BindContext;
2728
import org.springframework.boot.context.properties.bind.BindHandler;
@@ -33,6 +34,7 @@
3334
import org.springframework.core.ResolvableType;
3435
import org.springframework.util.ObjectUtils;
3536
import org.springframework.validation.AbstractBindingResult;
37+
import org.springframework.validation.BeanPropertyBindingResult;
3638
import org.springframework.validation.Validator;
3739

3840
/**
@@ -143,14 +145,14 @@ private void validateAndPush(ConfigurationPropertyName name, Object target, Clas
143145
/**
144146
* {@link AbstractBindingResult} implementation backed by the bound properties.
145147
*/
146-
private class ValidationResult extends AbstractBindingResult {
148+
private class ValidationResult extends BeanPropertyBindingResult {
147149

148150
private final ConfigurationPropertyName name;
149151

150-
private Object target;
152+
private final Object target;
151153

152154
protected ValidationResult(ConfigurationPropertyName name, Object target) {
153-
super(null);
155+
super(target, null);
154156
this.name = name;
155157
this.target = target;
156158
}
@@ -160,11 +162,6 @@ public String getObjectName() {
160162
return this.name.toString();
161163
}
162164

163-
@Override
164-
public Object getTarget() {
165-
return this.target;
166-
}
167-
168165
@Override
169166
public Class<?> getFieldType(String field) {
170167
ResolvableType type = getBoundField(ValidationBindHandler.this.boundTypes, field);
@@ -177,7 +174,29 @@ public Class<?> getFieldType(String field) {
177174

178175
@Override
179176
protected Object getActualFieldValue(String field) {
180-
return getBoundField(ValidationBindHandler.this.boundResults, field);
177+
Object boundField = getBoundField(ValidationBindHandler.this.boundResults, field);
178+
if (boundField != null) {
179+
return boundField;
180+
}
181+
try {
182+
return super.getActualFieldValue(field);
183+
}
184+
catch (Exception ex) {
185+
if (isPropertyNotReadable(ex)) {
186+
return null;
187+
}
188+
throw ex;
189+
}
190+
}
191+
192+
private boolean isPropertyNotReadable(Throwable ex) {
193+
while (ex != null) {
194+
if (ex instanceof NotReadablePropertyException) {
195+
return true;
196+
}
197+
ex = ex.getCause();
198+
}
199+
return false;
181200
}
182201

183202
private <T> T getBoundField(Map<ConfigurationPropertyName, T> boundFields, String field) {

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,13 @@ void loadWhenConfigurationPropertiesIsAlsoValidatorShouldApplyValidator() {
687687
});
688688
}
689689

690+
@Test
691+
void loadWhenConfigurationPropertiesWithValidDefaultValuesShouldNotFail() {
692+
AnnotationConfigApplicationContext context = load(ValidatorPropertiesWithDefaultValues.class);
693+
ValidatorPropertiesWithDefaultValues bean = context.getBean(ValidatorPropertiesWithDefaultValues.class);
694+
assertThat(bean.getBar()).isEqualTo("a");
695+
}
696+
690697
@Test
691698
void loadWhenSetterThrowsValidationExceptionShouldFail() {
692699
assertThatExceptionOfType(BeanCreationException.class)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2012-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.context.properties;
18+
19+
import org.springframework.validation.Errors;
20+
import org.springframework.validation.ValidationUtils;
21+
import org.springframework.validation.Validator;
22+
23+
/**
24+
* Used for testing validation of properties that have default field values.
25+
*
26+
* @author Madhura Bhave
27+
*/
28+
@EnableConfigurationProperties
29+
@ConfigurationProperties
30+
class ValidatorPropertiesWithDefaultValues implements Validator {
31+
32+
private String bar = "a";
33+
34+
@Override
35+
public boolean supports(Class<?> type) {
36+
return type == ValidatorPropertiesWithDefaultValues.class;
37+
}
38+
39+
@Override
40+
public void validate(Object target, Errors errors) {
41+
ValidationUtils.rejectIfEmpty(errors, "bar", "foo.empty");
42+
}
43+
44+
public String getBar() {
45+
return this.bar;
46+
}
47+
48+
public void setBar(String bar) {
49+
this.bar = bar;
50+
}
51+
52+
}

src/checkstyle/checkstyle-suppressions.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,5 @@
4949
<suppress files="LinuxDomainSocket" checks="FinalClass" message="SockaddrUn" />
5050
<suppress files="BsdDomainSocket" checks="FinalClass" message="SockaddrUn" />
5151
<suppress files="StringSequence" checks="SpringMethodVisibility" message="isEmpty"/>
52+
<suppress files="ValidatorPropertiesWithDefaultValues\.java" checks="SpringMethodVisibility" />
5253
</suppressions>

0 commit comments

Comments
 (0)