18
18
19
19
import java .util .Collection ;
20
20
import java .util .HashSet ;
21
+ import java .util .List ;
22
+ import java .util .Map ;
23
+ import java .util .Optional ;
21
24
import java .util .Set ;
22
25
23
26
import jakarta .validation .ConstraintValidator ;
24
27
import jakarta .validation .NoProviderFoundException ;
25
28
import jakarta .validation .Validation ;
26
29
import jakarta .validation .Validator ;
30
+ import jakarta .validation .ValidatorFactory ;
27
31
import jakarta .validation .metadata .BeanDescriptor ;
28
32
import jakarta .validation .metadata .ConstraintDescriptor ;
29
- import jakarta .validation .metadata .ConstructorDescriptor ;
30
- import jakarta .validation .metadata .MethodDescriptor ;
33
+ import jakarta .validation .metadata .ContainerElementTypeDescriptor ;
34
+ import jakarta .validation .metadata .ExecutableDescriptor ;
31
35
import jakarta .validation .metadata .MethodType ;
32
36
import jakarta .validation .metadata .ParameterDescriptor ;
33
37
import jakarta .validation .metadata .PropertyDescriptor ;
36
40
37
41
import org .springframework .aot .generate .GenerationContext ;
38
42
import org .springframework .aot .hint .MemberCategory ;
43
+ import org .springframework .aot .hint .ReflectionHints ;
39
44
import org .springframework .beans .factory .aot .BeanRegistrationAotContribution ;
40
45
import org .springframework .beans .factory .aot .BeanRegistrationAotProcessor ;
41
46
import org .springframework .beans .factory .aot .BeanRegistrationCode ;
42
47
import org .springframework .beans .factory .support .RegisteredBean ;
43
48
import org .springframework .core .KotlinDetector ;
49
+ import org .springframework .core .ResolvableType ;
44
50
import org .springframework .lang .Nullable ;
51
+ import org .springframework .util .Assert ;
45
52
import org .springframework .util .ClassUtils ;
53
+ import org .springframework .util .ReflectionUtils ;
46
54
47
55
/**
48
56
* AOT {@code BeanRegistrationAotProcessor} that adds additional hints
@@ -80,8 +88,8 @@ private static class BeanValidationDelegate {
80
88
81
89
@ Nullable
82
90
private static Validator getValidatorIfAvailable () {
83
- try {
84
- return Validation . buildDefaultValidatorFactory () .getValidator ();
91
+ try ( ValidatorFactory validator = Validation . buildDefaultValidatorFactory ()) {
92
+ return validator .getValidator ();
85
93
}
86
94
catch (NoProviderFoundException ex ) {
87
95
logger .info ("No Bean Validation provider available - skipping validation constraint hint inference" );
@@ -95,64 +103,134 @@ public static BeanRegistrationAotContribution processAheadOfTime(RegisteredBean
95
103
return null ;
96
104
}
97
105
106
+ Class <?> beanClass = registeredBean .getBeanClass ();
107
+ Set <Class <?>> validatedClasses = new HashSet <>();
108
+ Set <Class <? extends ConstraintValidator <?, ?>>> constraintValidatorClasses = new HashSet <>();
109
+
110
+ processAheadOfTime (beanClass , validatedClasses , constraintValidatorClasses );
111
+
112
+ if (!validatedClasses .isEmpty () || !constraintValidatorClasses .isEmpty ()) {
113
+ return new AotContribution (validatedClasses , constraintValidatorClasses );
114
+ }
115
+ return null ;
116
+ }
117
+
118
+ private static void processAheadOfTime (Class <?> clazz , Collection <Class <?>> validatedClasses ,
119
+ Collection <Class <? extends ConstraintValidator <?, ?>>> constraintValidatorClasses ) {
120
+
121
+ Assert .notNull (validator , "Validator can't be null" );
122
+
98
123
BeanDescriptor descriptor ;
99
124
try {
100
- descriptor = validator .getConstraintsForClass (registeredBean . getBeanClass () );
125
+ descriptor = validator .getConstraintsForClass (clazz );
101
126
}
102
127
catch (RuntimeException ex ) {
103
- if (KotlinDetector .isKotlinType (registeredBean . getBeanClass () ) && ex instanceof ArrayIndexOutOfBoundsException ) {
128
+ if (KotlinDetector .isKotlinType (clazz ) && ex instanceof ArrayIndexOutOfBoundsException ) {
104
129
// See https://hibernate.atlassian.net/browse/HV-1796 and https://youtrack.jetbrains.com/issue/KT-40857
105
- logger .warn ("Skipping validation constraint hint inference for bean " + registeredBean . getBeanName () +
130
+ logger .warn ("Skipping validation constraint hint inference for class " + clazz +
106
131
" due to an ArrayIndexOutOfBoundsException at validator level" );
107
132
}
108
133
else if (ex instanceof TypeNotPresentException ) {
109
- logger .debug ("Skipping validation constraint hint inference for bean " +
110
- registeredBean . getBeanName () + " due to a TypeNotPresentException at validator level: " + ex .getMessage ());
134
+ logger .debug ("Skipping validation constraint hint inference for class " +
135
+ clazz + " due to a TypeNotPresentException at validator level: " + ex .getMessage ());
111
136
}
112
137
else {
113
- logger .warn ("Skipping validation constraint hint inference for bean " +
114
- registeredBean .getBeanName (), ex );
138
+ logger .warn ("Skipping validation constraint hint inference for class " + clazz , ex );
115
139
}
116
- return null ;
140
+ return ;
117
141
}
118
142
119
- Set < ConstraintDescriptor <?>> constraintDescriptors = new HashSet <>( );
120
- for ( MethodDescriptor methodDescriptor : descriptor .getConstrainedMethods ( MethodType . NON_GETTER , MethodType . GETTER )) {
121
- for ( ParameterDescriptor parameterDescriptor : methodDescriptor . getParameterDescriptors ()) {
122
- constraintDescriptors . addAll ( parameterDescriptor . getConstraintDescriptors ());
123
- }
143
+ processExecutableDescriptor ( descriptor . getConstrainedMethods ( MethodType . NON_GETTER , MethodType . GETTER ), constraintValidatorClasses );
144
+ processExecutableDescriptor ( descriptor .getConstrainedConstructors (), constraintValidatorClasses );
145
+ processPropertyDescriptors ( descriptor . getConstrainedProperties (), constraintValidatorClasses );
146
+ if (! constraintValidatorClasses . isEmpty () && shouldProcess ( clazz )) {
147
+ validatedClasses . add ( clazz );
124
148
}
125
- for (ConstructorDescriptor constructorDescriptor : descriptor .getConstrainedConstructors ()) {
126
- for (ParameterDescriptor parameterDescriptor : constructorDescriptor .getParameterDescriptors ()) {
127
- constraintDescriptors .addAll (parameterDescriptor .getConstraintDescriptors ());
149
+
150
+ ReflectionUtils .doWithFields (clazz , field -> {
151
+ Class <?> type = field .getType ();
152
+ if (Iterable .class .isAssignableFrom (type ) || List .class .isAssignableFrom (type ) || Optional .class .isAssignableFrom (type )) {
153
+ ResolvableType resolvableType = ResolvableType .forField (field );
154
+ Class <?> genericType = resolvableType .getGeneric (0 ).toClass ();
155
+ if (shouldProcess (genericType )) {
156
+ validatedClasses .add (clazz );
157
+ processAheadOfTime (genericType , validatedClasses , constraintValidatorClasses );
158
+ }
159
+ }
160
+ if (Map .class .isAssignableFrom (type )) {
161
+ ResolvableType resolvableType = ResolvableType .forField (field );
162
+ Class <?> keyGenericType = resolvableType .getGeneric (0 ).toClass ();
163
+ Class <?> valueGenericType = resolvableType .getGeneric (1 ).toClass ();
164
+ if (shouldProcess (keyGenericType )) {
165
+ validatedClasses .add (clazz );
166
+ processAheadOfTime (keyGenericType , validatedClasses , constraintValidatorClasses );
167
+ }
168
+ if (shouldProcess (valueGenericType )) {
169
+ validatedClasses .add (clazz );
170
+ processAheadOfTime (valueGenericType , validatedClasses , constraintValidatorClasses );
171
+ }
172
+ }
173
+ });
174
+ }
175
+
176
+ private static boolean shouldProcess (Class <?> clazz ) {
177
+ return !clazz .getCanonicalName ().startsWith ("java." );
178
+ }
179
+
180
+ private static void processExecutableDescriptor (Set <? extends ExecutableDescriptor > executableDescriptors ,
181
+ Collection <Class <? extends ConstraintValidator <?, ?>>> constraintValidatorClasses ) {
182
+
183
+ for (ExecutableDescriptor executableDescriptor : executableDescriptors ) {
184
+ for (ParameterDescriptor parameterDescriptor : executableDescriptor .getParameterDescriptors ()) {
185
+ for (ConstraintDescriptor <?> constraintDescriptor : parameterDescriptor .getConstraintDescriptors ()) {
186
+ constraintValidatorClasses .addAll (constraintDescriptor .getConstraintValidatorClasses ());
187
+ }
188
+ for (ContainerElementTypeDescriptor typeDescriptor : parameterDescriptor .getConstrainedContainerElementTypes ()) {
189
+ for (ConstraintDescriptor <?> constraintDescriptor : typeDescriptor .getConstraintDescriptors ()) {
190
+ constraintValidatorClasses .addAll (constraintDescriptor .getConstraintValidatorClasses ());
191
+ }
192
+ }
128
193
}
129
194
}
130
- for (PropertyDescriptor propertyDescriptor : descriptor .getConstrainedProperties ()) {
131
- constraintDescriptors .addAll (propertyDescriptor .getConstraintDescriptors ());
132
- }
133
- if (!constraintDescriptors .isEmpty ()) {
134
- return new AotContribution (constraintDescriptors );
195
+ }
196
+
197
+ private static void processPropertyDescriptors (Set <PropertyDescriptor > propertyDescriptors ,
198
+ Collection <Class <? extends ConstraintValidator <?, ?>>> constraintValidatorClasses ) {
199
+
200
+ for (PropertyDescriptor propertyDescriptor : propertyDescriptors ) {
201
+ for (ConstraintDescriptor <?> constraintDescriptor : propertyDescriptor .getConstraintDescriptors ()) {
202
+ constraintValidatorClasses .addAll (constraintDescriptor .getConstraintValidatorClasses ());
203
+ }
204
+ for (ContainerElementTypeDescriptor typeDescriptor : propertyDescriptor .getConstrainedContainerElementTypes ()) {
205
+ for (ConstraintDescriptor <?> constraintDescriptor : typeDescriptor .getConstraintDescriptors ()) {
206
+ constraintValidatorClasses .addAll (constraintDescriptor .getConstraintValidatorClasses ());
207
+ }
208
+ }
135
209
}
136
- return null ;
137
210
}
138
211
}
139
212
140
213
141
214
private static class AotContribution implements BeanRegistrationAotContribution {
142
215
143
- private final Collection <ConstraintDescriptor <?>> constraintDescriptors ;
216
+ private final Collection <Class <?>> validatedClasses ;
217
+ private final Collection <Class <? extends ConstraintValidator <?, ?>>> constraintValidatorClasses ;
144
218
145
- public AotContribution (Collection <ConstraintDescriptor <?>> constraintDescriptors ) {
146
- this .constraintDescriptors = constraintDescriptors ;
219
+ public AotContribution (Collection <Class <?>> validatedClasses ,
220
+ Collection <Class <? extends ConstraintValidator <?, ?>>> constraintValidatorClasses ) {
221
+
222
+ this .validatedClasses = validatedClasses ;
223
+ this .constraintValidatorClasses = constraintValidatorClasses ;
147
224
}
148
225
149
226
@ Override
150
227
public void applyTo (GenerationContext generationContext , BeanRegistrationCode beanRegistrationCode ) {
151
- for (ConstraintDescriptor <?> constraintDescriptor : this .constraintDescriptors ) {
152
- for (Class <?> constraintValidatorClass : constraintDescriptor .getConstraintValidatorClasses ()) {
153
- generationContext .getRuntimeHints ().reflection ().registerType (constraintValidatorClass ,
154
- MemberCategory .INVOKE_DECLARED_CONSTRUCTORS );
155
- }
228
+ ReflectionHints hints = generationContext .getRuntimeHints ().reflection ();
229
+ for (Class <?> validatedClass : this .validatedClasses ) {
230
+ hints .registerType (validatedClass , MemberCategory .DECLARED_FIELDS );
231
+ }
232
+ for (Class <? extends ConstraintValidator <?, ?>> constraintValidatorClass : this .constraintValidatorClasses ) {
233
+ hints .registerType (constraintValidatorClass , MemberCategory .INVOKE_DECLARED_CONSTRUCTORS );
156
234
}
157
235
}
158
236
}
0 commit comments