diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java index d127e46e039..8a1ddc5a276 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractTestContextBootstrapper.java @@ -200,8 +200,9 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot * @see SpringFactoriesLoader#load(Class, FailureHandler) */ protected List getDefaultTestExecutionListeners() { - List listeners = SpringFactoriesLoader.forDefaultResourceLocation() - .load(TestExecutionListener.class, this::handleListenerInstantiationFailure); + SpringFactoriesLoader loader = SpringFactoriesLoader.forDefaultResourceLocation(getClass().getClassLoader()); + List listeners = + loader.load(TestExecutionListener.class, this::handleInstantiationFailure); if (logger.isDebugEnabled()) { logger.debug("Loaded default TestExecutionListener implementations from location [%s]: %s" .formatted(SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION, classNames(listeners))); @@ -219,7 +220,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot catch (BeanInstantiationException ex) { Throwable cause = ex.getCause(); if (cause instanceof ClassNotFoundException || cause instanceof NoClassDefFoundError) { - logSkippedListener(listenerClass.getName(), cause); + logSkippedComponent(TestExecutionListener.class, listenerClass.getName(), cause); } else { throw ex; @@ -229,46 +230,6 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot return listeners; } - private void handleListenerInstantiationFailure( - Class factoryType, String listenerClassName, Throwable failure) { - - Throwable ex = (failure instanceof InvocationTargetException ite ? - ite.getTargetException() : failure); - - if (ex instanceof ClassNotFoundException || ex instanceof NoClassDefFoundError) { - logSkippedListener(listenerClassName, ex); - } - else if (ex instanceof LinkageError) { - if (logger.isDebugEnabled()) { - logger.debug(""" - Could not load default TestExecutionListener [%s]. Specify custom \ - listener classes or make the default listener classes available.""" - .formatted(listenerClassName), ex); - } - } - else { - if (ex instanceof RuntimeException runtimeException) { - throw runtimeException; - } - if (ex instanceof Error error) { - throw error; - } - throw new IllegalStateException( - "Failed to load default TestExecutionListener [%s].".formatted(listenerClassName), ex); - } - } - - private void logSkippedListener(String listenerClassName, Throwable ex) { - // TestExecutionListener not applicable due to a missing dependency - if (logger.isDebugEnabled()) { - logger.debug(""" - Skipping candidate TestExecutionListener [%s] due to a missing dependency. \ - Specify custom listener classes or make the default listener classes \ - and their required dependencies available. Offending class: [%s]""" - .formatted(listenerClassName, ex.getMessage())); - } - } - /** * {@inheritDoc} */ @@ -440,8 +401,9 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot * @see SpringFactoriesLoader#loadFactories */ protected List getContextCustomizerFactories() { + SpringFactoriesLoader loader = SpringFactoriesLoader.forDefaultResourceLocation(getClass().getClassLoader()); List factories = - SpringFactoriesLoader.loadFactories(ContextCustomizerFactory.class, getClass().getClassLoader()); + loader.load(ContextCustomizerFactory.class, this::handleInstantiationFailure); if (logger.isDebugEnabled()) { logger.debug("Loaded ContextCustomizerFactory implementations from location [%s]: %s" .formatted(SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION, classNames(factories))); @@ -570,6 +532,45 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot } + private void handleInstantiationFailure( + Class factoryType, String factoryImplementationName, Throwable failure) { + + Throwable ex = (failure instanceof InvocationTargetException ite ? + ite.getTargetException() : failure); + if (ex instanceof ClassNotFoundException || ex instanceof NoClassDefFoundError) { + logSkippedComponent(factoryType, factoryImplementationName, ex); + } + else if (ex instanceof LinkageError) { + if (logger.isDebugEnabled()) { + logger.debug(""" + Could not load %1$s [%2$s]. Specify custom %1$s classes or make the default %1$s classes \ + available.""".formatted(factoryType.getSimpleName(), factoryImplementationName), ex); + } + } + else { + if (ex instanceof RuntimeException runtimeException) { + throw runtimeException; + } + if (ex instanceof Error error) { + throw error; + } + throw new IllegalStateException( + "Failed to load %s [%s].".formatted(factoryType.getSimpleName(), factoryImplementationName), ex); + } + } + + private void logSkippedComponent(Class factoryType, String factoryImplementationName, Throwable ex) { + // TestExecutionListener/ContextCustomizerFactory not applicable due to a missing dependency + if (logger.isDebugEnabled()) { + logger.debug(""" + Skipping candidate %1$s [%2$s] due to a missing dependency. \ + Specify custom %1$s classes or make the default %1$s classes \ + and their required dependencies available. Offending class: [%3$s]""" + .formatted(factoryType.getSimpleName(), factoryImplementationName, ex.getMessage())); + } + } + + private static List classNames(List components) { return components.stream().map(Object::getClass).map(Class::getName).toList(); }