21
21
import java .beans .Introspector ;
22
22
import java .beans .PropertyDescriptor ;
23
23
import java .lang .reflect .Constructor ;
24
+ import java .lang .reflect .Field ;
24
25
import java .lang .reflect .Method ;
25
26
import java .util .Arrays ;
26
27
import java .util .Collection ;
34
35
import org .springframework .beans .ExtendedBeanInfoFactory ;
35
36
import org .springframework .boot .context .properties .bind .Bindable ;
36
37
import org .springframework .core .ResolvableType ;
38
+ import org .springframework .core .annotation .MergedAnnotations ;
39
+ import org .springframework .util .ReflectionUtils ;
37
40
38
41
/**
39
42
* Registers a given type on {@link ReflectionHints} for binding purposes, discovering any
40
- * referenced types it may expose via a property.
43
+ * nested type it may expose via a property.
41
44
*
42
45
* @author Andy Wilkinson
43
46
* @author Moritz Halbritter
@@ -74,22 +77,12 @@ public static void processConfigurationProperties(Class<?> type, ReflectionHints
74
77
.process (reflectionHints );
75
78
}
76
79
77
- private void processType (Class <?> type , ReflectionHints reflectionHints ) {
78
- if (isTypeIgnored (type )) {
79
- return ;
80
- }
81
- new ConfigurationPropertiesReflectionHintsProcessor (type , getBindConstructor (type , true ), this .seen )
82
- .process (reflectionHints );
80
+ private void processNestedType (Class <?> type , ReflectionHints reflectionHints ) {
81
+ processNestedType (type , getBindConstructor (type , true ), reflectionHints );
83
82
}
84
83
85
- private boolean isTypeIgnored (Class <?> type ) {
86
- if (type .getPackageName ().startsWith ("java." )) {
87
- return true ;
88
- }
89
- if (type .isInterface ()) {
90
- return true ;
91
- }
92
- return false ;
84
+ private void processNestedType (Class <?> type , Constructor <?> bindConstructor , ReflectionHints reflectionHints ) {
85
+ new ConfigurationPropertiesReflectionHintsProcessor (type , bindConstructor , this .seen ).process (reflectionHints );
93
86
}
94
87
95
88
private static Constructor <?> getBindConstructor (Class <?> type , boolean nestedType ) {
@@ -125,8 +118,9 @@ private void handleConstructor(ReflectionHints reflectionHints) {
125
118
126
119
private void handleValueObjectProperties (ReflectionHints reflectionHints ) {
127
120
for (int i = 0 ; i < this .bindConstructor .getParameterCount (); i ++) {
121
+ String propertyName = this .bindConstructor .getParameters ()[i ].getName ();
128
122
ResolvableType propertyType = ResolvableType .forConstructorParameter (this .bindConstructor , i );
129
- registerType (reflectionHints , propertyType );
123
+ handleProperty (reflectionHints , propertyName , propertyType );
130
124
}
131
125
}
132
126
@@ -135,12 +129,16 @@ private void handleJavaBeanProperties(ReflectionHints reflectionHints) {
135
129
Method readMethod = propertyDescriptor .getReadMethod ();
136
130
if (readMethod != null ) {
137
131
ResolvableType propertyType = ResolvableType .forMethodReturnType (readMethod , this .type );
138
- registerType (reflectionHints , propertyType );
132
+ String propertyName = propertyDescriptor .getName ();
133
+ if (isSetterMandatory (propertyName , propertyType ) && propertyDescriptor .getWriteMethod () == null ) {
134
+ continue ;
135
+ }
136
+ handleProperty (reflectionHints , propertyName , propertyType );
139
137
}
140
138
}
141
139
}
142
140
143
- private void registerType (ReflectionHints reflectionHints , ResolvableType propertyType ) {
141
+ private void handleProperty (ReflectionHints reflectionHints , String propertyName , ResolvableType propertyType ) {
144
142
Class <?> propertyClass = propertyType .resolve ();
145
143
if (propertyClass == null ) {
146
144
return ;
@@ -150,11 +148,25 @@ private void registerType(ReflectionHints reflectionHints, ResolvableType proper
150
148
}
151
149
Class <?> componentType = getComponentType (propertyType );
152
150
if (componentType != null ) {
153
- processType (componentType , reflectionHints );
151
+ // Can be a list of simple types
152
+ if (!isJavaType (componentType )) {
153
+ processNestedType (componentType , reflectionHints );
154
+ }
154
155
}
155
- else {
156
- processType (propertyClass , reflectionHints );
156
+ else if (isNestedType (propertyName , propertyClass )) {
157
+ processNestedType (propertyClass , reflectionHints );
158
+ }
159
+ }
160
+
161
+ private boolean isSetterMandatory (String propertyName , ResolvableType propertyType ) {
162
+ Class <?> propertyClass = propertyType .resolve ();
163
+ if (propertyClass == null ) {
164
+ return true ;
157
165
}
166
+ if (getComponentType (propertyType ) != null ) {
167
+ return false ;
168
+ }
169
+ return !isNestedType (propertyName , propertyClass );
158
170
}
159
171
160
172
private Class <?> getComponentType (ResolvableType propertyType ) {
@@ -171,6 +183,27 @@ else if (Map.class.isAssignableFrom(propertyClass)) {
171
183
return null ;
172
184
}
173
185
186
+ /**
187
+ * Specify whether the specified property refer to a nested type. A nested type
188
+ * represents a sub-namespace that need to be fully resolved.
189
+ * @param propertyName the name of the property
190
+ * @param propertyType the type of the property
191
+ * @return whether the specified {@code propertyType} is a nested type
192
+ */
193
+ private boolean isNestedType (String propertyName , Class <?> propertyType ) {
194
+ if (this .type .equals (propertyType .getDeclaringClass ())) {
195
+ return true ;
196
+ }
197
+ else {
198
+ Field field = ReflectionUtils .findField (this .type , propertyName );
199
+ return field != null && MergedAnnotations .from (field ).isPresent (NestedConfigurationProperty .class );
200
+ }
201
+ }
202
+
203
+ private boolean isJavaType (Class <?> candidate ) {
204
+ return candidate .getPackageName ().startsWith ("java." );
205
+ }
206
+
174
207
private static BeanInfo getBeanInfo (Class <?> beanType ) {
175
208
try {
176
209
BeanInfo beanInfo = beanInfoFactory .getBeanInfo (beanType );
0 commit comments