mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
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
This commit is contained in:
parent
e5aea89599
commit
869141766b
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2019 the original author or authors.
|
* Copyright 2012-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -21,9 +21,12 @@ import java.lang.annotation.Annotation;
|
|||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
@ -124,13 +127,19 @@ class JavaBeanBinder implements DataObjectBinder {
|
|||||||
|
|
||||||
private void addProperties(Class<?> type) {
|
private void addProperties(Class<?> type) {
|
||||||
while (type != null && !Object.class.equals(type)) {
|
while (type != null && !Object.class.equals(type)) {
|
||||||
Method[] declaredMethods = type.getDeclaredMethods();
|
Method[] declaredMethods = getSorted(type, Class::getDeclaredMethods, Method::getName);
|
||||||
Field[] declaredFields = type.getDeclaredFields();
|
Field[] declaredFields = getSorted(type, Class::getDeclaredFields, Field::getName);
|
||||||
addProperties(declaredMethods, declaredFields);
|
addProperties(declaredMethods, declaredFields);
|
||||||
type = type.getSuperclass();
|
type = type.getSuperclass();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private <S, E> E[] getSorted(S source, Function<S, E[]> elements, Function<E, String> name) {
|
||||||
|
E[] result = elements.apply(source);
|
||||||
|
Arrays.sort(result, Comparator.comparing(name));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
protected void addProperties(Method[] declaredMethods, Field[] declaredFields) {
|
protected void addProperties(Method[] declaredMethods, Field[] declaredFields) {
|
||||||
for (int i = 0; i < declaredMethods.length; i++) {
|
for (int i = 0; i < declaredMethods.length; i++) {
|
||||||
if (!isCandidate(declaredMethods[i])) {
|
if (!isCandidate(declaredMethods[i])) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2012-2019 the original author or authors.
|
* Copyright 2012-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -26,6 +26,7 @@ import java.util.LinkedHashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@ -540,6 +541,19 @@ class JavaBeanBinderTests {
|
|||||||
assertThat(bean.getProperty()).isEqualTo("test");
|
assertThat(bean.getProperty()).isEqualTo("test");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void bindUsesConsistentPropertyOrder() {
|
||||||
|
MockConfigurationPropertySource source = new MockConfigurationPropertySource();
|
||||||
|
source.put("foo.gamma", "0");
|
||||||
|
source.put("foo.alpha", "0");
|
||||||
|
source.put("foo.beta", "0");
|
||||||
|
this.sources.add(source);
|
||||||
|
PropertyOrderBean bean = this.binder.bind("foo", Bindable.of(PropertyOrderBean.class)).get();
|
||||||
|
assertThat(bean.getAlpha()).isEqualTo(0);
|
||||||
|
assertThat(bean.getBeta()).isEqualTo(1);
|
||||||
|
assertThat(bean.getGamma()).isEqualTo(2);
|
||||||
|
}
|
||||||
|
|
||||||
static class ExampleValueBean {
|
static class ExampleValueBean {
|
||||||
|
|
||||||
private int intValue;
|
private int intValue;
|
||||||
@ -1036,4 +1050,40 @@ class JavaBeanBinderTests {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class PropertyOrderBean {
|
||||||
|
|
||||||
|
static AtomicInteger atomic = new AtomicInteger();
|
||||||
|
|
||||||
|
private int alpha;
|
||||||
|
|
||||||
|
private int beta;
|
||||||
|
|
||||||
|
private int gamma;
|
||||||
|
|
||||||
|
int getAlpha() {
|
||||||
|
return this.alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setAlpha(int alpha) {
|
||||||
|
this.alpha = alpha + atomic.getAndIncrement();
|
||||||
|
}
|
||||||
|
|
||||||
|
int getBeta() {
|
||||||
|
return this.beta;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBeta(int beta) {
|
||||||
|
this.beta = beta + atomic.getAndIncrement();
|
||||||
|
}
|
||||||
|
|
||||||
|
int getGamma() {
|
||||||
|
return this.gamma;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setGamma(int gamma) {
|
||||||
|
this.gamma = gamma + atomic.getAndIncrement();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user