29
29
import java .util .List ;
30
30
import java .util .Map ;
31
31
import java .util .Properties ;
32
+ import java .util .function .BiConsumer ;
33
+ import java .util .function .BiFunction ;
32
34
import java .util .function .Function ;
33
35
import java .util .function .Supplier ;
34
36
@@ -95,6 +97,8 @@ public final class SpringFactoriesLoader {
95
97
96
98
private static final Log logger = LogFactory .getLog (SpringFactoriesLoader .class );
97
99
100
+ private static final FailureHandler THROWING_HANDLER = FailureHandler .throwing ();
101
+
98
102
static final Map <ClassLoader , Map <String , List <String >>> cache = new ConcurrentReferenceHashMap <>();
99
103
100
104
@@ -115,7 +119,7 @@ private SpringFactoriesLoader() {
115
119
* be loaded or if an error occurs while instantiating any factory
116
120
*/
117
121
public static <T > List <T > loadFactories (Class <T > factoryType , @ Nullable ClassLoader classLoader ) {
118
- return loadFactories (factoryType , classLoader , null );
122
+ return loadFactories (factoryType , classLoader , null , null );
119
123
}
120
124
121
125
/**
@@ -135,13 +139,59 @@ public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoa
135
139
public static <T > List <T > loadFactories (Class <T > factoryType , @ Nullable ClassLoader classLoader ,
136
140
@ Nullable ArgumentResolver argumentResolver ) {
137
141
142
+ return loadFactories (factoryType , classLoader , argumentResolver , null );
143
+ }
144
+
145
+ /**
146
+ * Load and instantiate the factory implementations of the given type from
147
+ * {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader with custom failure
148
+ * handling provided by the given failure handler.
149
+ * <p>The returned factories are sorted through {@link AnnotationAwareOrderComparator}.
150
+ * <p>As of Spring Framework 5.3, if duplicate implementation class names are
151
+ * discovered for a given factory type, only one instance of the duplicated
152
+ * implementation type will be instantiated.
153
+ * <p>For any factory implementation class that cannot be loaded or error that occurs while
154
+ * instantiating it, the given failure handler is called.
155
+ * @param factoryType the interface or abstract class representing the factory
156
+ * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default)
157
+ * @param failureHandler the FactoryInstantiationFailureHandler to use for handling of factory instantiation failures
158
+ * @since 6.0
159
+ */
160
+ public static <T > List <T > loadFactories (Class <T > factoryType , @ Nullable ClassLoader classLoader ,
161
+ @ Nullable FailureHandler failureHandler ) {
162
+
163
+ return loadFactories (factoryType , classLoader , null , failureHandler );
164
+ }
165
+
166
+ /**
167
+ * Load and instantiate the factory implementations of the given type from
168
+ * {@value #FACTORIES_RESOURCE_LOCATION}, using the given arguments and class loader with custom
169
+ * failure handling provided by the given failure handler.
170
+ * <p>The returned factories are sorted through {@link AnnotationAwareOrderComparator}.
171
+ * <p>As of Spring Framework 5.3, if duplicate implementation class names are
172
+ * discovered for a given factory type, only one instance of the duplicated
173
+ * implementation type will be instantiated.
174
+ * <p>For any factory implementation class that cannot be loaded or error that occurs while
175
+ * instantiating it, the given failure handler is called.
176
+ * @param factoryType the interface or abstract class representing the factory
177
+ * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default)
178
+ * @param argumentResolver strategy used to resolve constructor arguments by their type
179
+ * @param failureHandler the FactoryInstantiationFailureHandler to use for handling of factory
180
+ * instantiation failures
181
+ * @since 6.0
182
+ */
183
+ public static <T > List <T > loadFactories (Class <T > factoryType , @ Nullable ClassLoader classLoader ,
184
+ @ Nullable ArgumentResolver argumentResolver , @ Nullable FailureHandler failureHandler ) {
185
+
138
186
Assert .notNull (factoryType , "'factoryType' must not be null" );
139
187
ClassLoader classLoaderToUse = (classLoader != null ) ? classLoader : SpringFactoriesLoader .class .getClassLoader ();
140
188
List <String > factoryImplementationNames = loadFactoryNames (factoryType , classLoaderToUse );
141
189
logger .trace (LogMessage .format ("Loaded [%s] names: %s" , factoryType .getName (), factoryImplementationNames ));
142
190
List <T > result = new ArrayList <>(factoryImplementationNames .size ());
191
+ FailureHandler failureHandlerToUse = (failureHandler != null ) ? failureHandler : THROWING_HANDLER ;
143
192
for (String factoryImplementationName : factoryImplementationNames ) {
144
- T factory = instantiateFactory (factoryImplementationName , factoryType , argumentResolver , classLoaderToUse );
193
+ T factory = instantiateFactory (factoryImplementationName , factoryType ,
194
+ argumentResolver , classLoaderToUse , failureHandlerToUse );
145
195
if (factory != null ) {
146
196
result .add (factory );
147
197
}
@@ -213,7 +263,7 @@ private static List<String> toDistinctUnmodifiableList(String factoryType, List<
213
263
@ Nullable
214
264
private static <T > T instantiateFactory (String factoryImplementationName ,
215
265
Class <T > factoryType , @ Nullable ArgumentResolver argumentResolver ,
216
- ClassLoader classLoader ) {
266
+ ClassLoader classLoader , FailureHandler failureHandler ) {
217
267
try {
218
268
Class <?> factoryImplementationClass = ClassUtils .forName (factoryImplementationName , classLoader );
219
269
Assert .isTrue (factoryType .isAssignableFrom (factoryImplementationClass ),
@@ -222,8 +272,8 @@ private static <T> T instantiateFactory(String factoryImplementationName,
222
272
return factoryInstantiator .instantiate (argumentResolver );
223
273
}
224
274
catch (Throwable ex ) {
225
- throw new IllegalArgumentException ( "Unable to instantiate factory class [" + factoryImplementationName +
226
- "] for factory type [" + factoryType . getName () + "]" , ex ) ;
275
+ failureHandler . handleFailure ( factoryType , factoryImplementationName , ex );
276
+ return null ;
227
277
}
228
278
}
229
279
@@ -353,6 +403,75 @@ private static <T> T instantiate(Constructor<T> constructor, KFunction<T> kotlin
353
403
}
354
404
355
405
406
+ /**
407
+ * Strategy for handling a failure that occurs when instantiating a factory.
408
+ *
409
+ * @since 6.0
410
+ * @see FailureHandler#throwing()
411
+ * @see FailureHandler#logging(Log)
412
+ */
413
+ @ FunctionalInterface
414
+ public interface FailureHandler {
415
+
416
+ /**
417
+ * Handle the {@code failure} that occurred when instantiating the {@code factoryImplementationName}
418
+ * that was expected to be of the given {@code factoryType}.
419
+ * @param factoryType the type of the factory
420
+ * @param factoryImplementationName the name of the factory implementation
421
+ * @param failure the failure that occurred
422
+ * @see #throwing()
423
+ * @see #logging
424
+ */
425
+ void handleFailure (Class <?> factoryType , String factoryImplementationName , Throwable failure );
426
+
427
+ /**
428
+ * Return a new {@link FailureHandler} that handles
429
+ * errors by throwing an {@link IllegalArgumentException}.
430
+ * @return a new {@link FailureHandler} instance
431
+ */
432
+ static FailureHandler throwing () {
433
+ return throwing (IllegalArgumentException ::new );
434
+ }
435
+
436
+ /**
437
+ * Return a new {@link FailureHandler} that handles
438
+ * errors by throwing an exception.
439
+ * @param exceptionFactory factory used to create the exception
440
+ * @return a new {@link FailureHandler} instance
441
+ */
442
+ static FailureHandler throwing (BiFunction <String , Throwable , ? extends RuntimeException > exceptionFactory ) {
443
+ return handleMessage ((message , failure ) -> {
444
+ throw exceptionFactory .apply (message .get (), failure );
445
+ });
446
+ }
447
+
448
+ /**
449
+ * Return a new {@link FailureHandler} that handles
450
+ * errors by logging trace messages.
451
+ * @param logger the logger used to log message
452
+ * @return a new {@link FailureHandler} instance
453
+ */
454
+ static FailureHandler logging (Log logger ) {
455
+ return handleMessage ((message , failure ) -> logger .trace (LogMessage .of (message ), failure ));
456
+ }
457
+
458
+ /**
459
+ * Return a new {@link FailureHandler} that handles
460
+ * errors with using a standard formatted message.
461
+ * @param messageHandler the message handler used to handle the problem
462
+ * @return a new {@link FailureHandler} instance
463
+ */
464
+ static FailureHandler handleMessage (BiConsumer <Supplier <String >, Throwable > messageHandler ) {
465
+ return (factoryType , factoryImplementationName , failure ) -> {
466
+ Supplier <String > message = () -> "Unable to instantiate factory class [" + factoryImplementationName +
467
+ "] for factory type [" + factoryType .getName () + "]" ;
468
+ messageHandler .accept (message , failure );
469
+ };
470
+ }
471
+
472
+ }
473
+
474
+
356
475
/**
357
476
* Strategy for resolving constructor arguments based on their type.
358
477
*
0 commit comments