Skip to content

Commit 8691417

Browse files
committed
Use deterministic ordering of JavaBean methods
Update `JavaBeanBinder` so that methods and fields are sorted before being processed. This ensures that setters are called in a deterministic order, rather than the unspecified and variable order that reflection provides. Fixes gh-24068
1 parent e5aea89 commit 8691417

File tree

2 files changed

+63
-4
lines changed

2 files changed

+63
-4
lines changed

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,9 +21,12 @@
2121
import java.lang.reflect.Field;
2222
import java.lang.reflect.Method;
2323
import java.lang.reflect.Modifier;
24+
import java.util.Arrays;
25+
import java.util.Comparator;
2426
import java.util.LinkedHashMap;
2527
import java.util.Map;
2628
import java.util.function.BiConsumer;
29+
import java.util.function.Function;
2730
import java.util.function.Supplier;
2831

2932
import org.springframework.beans.BeanUtils;
@@ -124,13 +127,19 @@ static class Bean<T> {
124127

125128
private void addProperties(Class<?> type) {
126129
while (type != null && !Object.class.equals(type)) {
127-
Method[] declaredMethods = type.getDeclaredMethods();
128-
Field[] declaredFields = type.getDeclaredFields();
130+
Method[] declaredMethods = getSorted(type, Class::getDeclaredMethods, Method::getName);
131+
Field[] declaredFields = getSorted(type, Class::getDeclaredFields, Field::getName);
129132
addProperties(declaredMethods, declaredFields);
130133
type = type.getSuperclass();
131134
}
132135
}
133136

137+
private <S, E> E[] getSorted(S source, Function<S, E[]> elements, Function<E, String> name) {
138+
E[] result = elements.apply(source);
139+
Arrays.sort(result, Comparator.comparing(name));
140+
return result;
141+
}
142+
134143
protected void addProperties(Method[] declaredMethods, Field[] declaredFields) {
135144
for (int i = 0; i < declaredMethods.length; i++) {
136145
if (!isCandidate(declaredMethods[i])) {

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

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
2626
import java.util.List;
2727
import java.util.Map;
2828
import java.util.Set;
29+
import java.util.concurrent.atomic.AtomicInteger;
2930

3031
import org.junit.jupiter.api.Test;
3132

@@ -540,6 +541,19 @@ void bindWhenHasPackagePrivateSetterShouldBind() {
540541
assertThat(bean.getProperty()).isEqualTo("test");
541542
}
542543

544+
@Test
545+
void bindUsesConsistentPropertyOrder() {
546+
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
547+
source.put("foo.gamma", "0");
548+
source.put("foo.alpha", "0");
549+
source.put("foo.beta", "0");
550+
this.sources.add(source);
551+
PropertyOrderBean bean = this.binder.bind("foo", Bindable.of(PropertyOrderBean.class)).get();
552+
assertThat(bean.getAlpha()).isEqualTo(0);
553+
assertThat(bean.getBeta()).isEqualTo(1);
554+
assertThat(bean.getGamma()).isEqualTo(2);
555+
}
556+
543557
static class ExampleValueBean {
544558

545559
private int intValue;
@@ -1036,4 +1050,40 @@ void setProperty(String property) {
10361050

10371051
}
10381052

1053+
static class PropertyOrderBean {
1054+
1055+
static AtomicInteger atomic = new AtomicInteger();
1056+
1057+
private int alpha;
1058+
1059+
private int beta;
1060+
1061+
private int gamma;
1062+
1063+
int getAlpha() {
1064+
return this.alpha;
1065+
}
1066+
1067+
void setAlpha(int alpha) {
1068+
this.alpha = alpha + atomic.getAndIncrement();
1069+
}
1070+
1071+
int getBeta() {
1072+
return this.beta;
1073+
}
1074+
1075+
void setBeta(int beta) {
1076+
this.beta = beta + atomic.getAndIncrement();
1077+
}
1078+
1079+
int getGamma() {
1080+
return this.gamma;
1081+
}
1082+
1083+
void setGamma(int gamma) {
1084+
this.gamma = gamma + atomic.getAndIncrement();
1085+
}
1086+
1087+
}
1088+
10391089
}

0 commit comments

Comments
 (0)