Stop using SpringFactoriesLoader.loadFactoryNames() in spring-test
Since SpringFactoriesLoader.loadFactoryNames() will be deprecated in gh-27954, this commit removes the use of it in the spring-test module. Specifically, this commit removes the protected getDefaultTestExecutionListenerClasses() and getDefaultTestExecutionListenerClassNames() methods from AbstractTestContextBootstrapper and replaces them with a new protected getDefaultTestExecutionListeners() method that makes use of new APIs introduced in SpringFactoriesLoader for 6.0. Third-party subclasses of AbstractTestContextBootstrapper that have overridden or used getDefaultTestExecutionListenerClasses() or getDefaultTestExecutionListenerClassNames() will therefore need to migrate to getDefaultTestExecutionListeners() in Spring Framework 6.0. Closes gh-28666
This commit is contained in:
parent
24c46142c6
commit
d1b65f6d3e
|
@ -24,6 +24,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.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
@ -32,6 +33,7 @@ import org.springframework.beans.BeanInstantiationException;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||||
|
import org.springframework.core.io.support.SpringFactoriesLoader.FailureHandler;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.test.context.BootstrapContext;
|
import org.springframework.test.context.BootstrapContext;
|
||||||
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
|
import org.springframework.test.context.CacheAwareContextLoaderDelegate;
|
||||||
|
@ -112,7 +114,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
|
||||||
public final List<TestExecutionListener> getTestExecutionListeners() {
|
public final List<TestExecutionListener> getTestExecutionListeners() {
|
||||||
Class<?> clazz = getBootstrapContext().getTestClass();
|
Class<?> clazz = getBootstrapContext().getTestClass();
|
||||||
Class<TestExecutionListeners> annotationType = TestExecutionListeners.class;
|
Class<TestExecutionListeners> annotationType = TestExecutionListeners.class;
|
||||||
List<Class<? extends TestExecutionListener>> classesList = new ArrayList<>();
|
List<TestExecutionListener> listeners = new ArrayList<>(8);
|
||||||
boolean usingDefaults = false;
|
boolean usingDefaults = false;
|
||||||
|
|
||||||
AnnotationDescriptor<TestExecutionListeners> descriptor =
|
AnnotationDescriptor<TestExecutionListeners> descriptor =
|
||||||
|
@ -125,7 +127,7 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
|
||||||
clazz.getName()));
|
clazz.getName()));
|
||||||
}
|
}
|
||||||
usingDefaults = true;
|
usingDefaults = true;
|
||||||
classesList.addAll(getDefaultTestExecutionListenerClasses());
|
listeners.addAll(getDefaultTestExecutionListeners());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Traverse the class hierarchy...
|
// Traverse the class hierarchy...
|
||||||
|
@ -149,24 +151,27 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
|
||||||
"@TestExecutionListeners for class [%s].", descriptor.getRootDeclaringClass().getName()));
|
"@TestExecutionListeners for class [%s].", descriptor.getRootDeclaringClass().getName()));
|
||||||
}
|
}
|
||||||
usingDefaults = true;
|
usingDefaults = true;
|
||||||
classesList.addAll(getDefaultTestExecutionListenerClasses());
|
listeners.addAll(getDefaultTestExecutionListeners());
|
||||||
}
|
}
|
||||||
|
|
||||||
classesList.addAll(0, Arrays.asList(testExecutionListeners.listeners()));
|
listeners.addAll(0, instantiateListeners(testExecutionListeners.listeners()));
|
||||||
|
|
||||||
descriptor = (inheritListeners ? parentDescriptor : null);
|
descriptor = (inheritListeners ? parentDescriptor : null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Collection<Class<? extends TestExecutionListener>> classesToUse = classesList;
|
if (usingDefaults) {
|
||||||
// Remove possible duplicates if we loaded default listeners.
|
// Remove possible duplicates if we loaded default listeners.
|
||||||
if (usingDefaults) {
|
List<TestExecutionListener> uniqueListeners = new ArrayList<>(listeners.size());
|
||||||
classesToUse = new LinkedHashSet<>(classesList);
|
listeners.forEach(listener -> {
|
||||||
|
Class<? extends TestExecutionListener> listenerClass = listener.getClass();
|
||||||
|
if (uniqueListeners.stream().map(Object::getClass).noneMatch(listenerClass::equals)) {
|
||||||
|
uniqueListeners.add(listener);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
listeners = uniqueListeners;
|
||||||
|
|
||||||
List<TestExecutionListener> listeners = instantiateListeners(classesToUse);
|
|
||||||
// Sort by Ordered/@Order if we loaded default listeners.
|
// Sort by Ordered/@Order if we loaded default listeners.
|
||||||
if (usingDefaults) {
|
|
||||||
AnnotationAwareOrderComparator.sort(listeners);
|
AnnotationAwareOrderComparator.sort(listeners);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,8 +181,61 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
|
||||||
return listeners;
|
return listeners;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<TestExecutionListener> instantiateListeners(Collection<Class<? extends TestExecutionListener>> classes) {
|
/**
|
||||||
List<TestExecutionListener> listeners = new ArrayList<>(classes.size());
|
* Get the default {@link TestExecutionListener TestExecutionListeners} for
|
||||||
|
* this bootstrapper.
|
||||||
|
* <p>This method is invoked by {@link #getTestExecutionListeners()}.
|
||||||
|
* <p>The default implementation looks up and instantiates all
|
||||||
|
* {@code org.springframework.test.context.TestExecutionListener} entries
|
||||||
|
* configured in all {@code META-INF/spring.factories} files on the classpath.
|
||||||
|
* <p>If a particular listener cannot be loaded due to a {@link LinkageError}
|
||||||
|
* or {@link ClassNotFoundException}, a {@code DEBUG} message will be logged,
|
||||||
|
* but the associated exception will not be rethrown. A {@link RuntimeException}
|
||||||
|
* or any other {@link Error} will be rethrown. Any other exception will be
|
||||||
|
* thrown wrapped in an {@link IllegalStateException}.
|
||||||
|
* @return an <em>unmodifiable</em> list of default {@code TestExecutionListener}
|
||||||
|
* instances
|
||||||
|
* @since 6.0
|
||||||
|
* @see SpringFactoriesLoader#forDefaultResourceLocation()
|
||||||
|
* @see SpringFactoriesLoader#load(Class, FailureHandler)
|
||||||
|
*/
|
||||||
|
protected List<TestExecutionListener> getDefaultTestExecutionListeners() {
|
||||||
|
FailureHandler failureHandler = (factoryType, factoryImplementationName, ex) -> {
|
||||||
|
if (ex instanceof LinkageError || ex instanceof ClassNotFoundException) {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("Could not load default TestExecutionListener [" + factoryImplementationName +
|
||||||
|
"]. Specify custom listener classes or make the default listener classes available.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (ex instanceof RuntimeException runtimeException) {
|
||||||
|
throw runtimeException;
|
||||||
|
}
|
||||||
|
if (ex instanceof Error error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("Failed to load default TestExecutionListener [" +
|
||||||
|
factoryImplementationName + "].", ex);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
List<TestExecutionListener> listeners = SpringFactoriesLoader.forDefaultResourceLocation()
|
||||||
|
.load(TestExecutionListener.class, failureHandler);
|
||||||
|
|
||||||
|
if (logger.isInfoEnabled()) {
|
||||||
|
List<String> classNames = listeners.stream()
|
||||||
|
.map(listener -> listener.getClass().getName())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
logger.info(String.format("Loaded default TestExecutionListener implementations from location [%s]: %s",
|
||||||
|
SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION, classNames));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.unmodifiableList(listeners);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private List<TestExecutionListener> instantiateListeners(Class<? extends TestExecutionListener>... classes) {
|
||||||
|
List<TestExecutionListener> listeners = new ArrayList<>(classes.length);
|
||||||
for (Class<? extends TestExecutionListener> listenerClass : classes) {
|
for (Class<? extends TestExecutionListener> listenerClass : classes) {
|
||||||
try {
|
try {
|
||||||
listeners.add(BeanUtils.instantiateClass(listenerClass));
|
listeners.add(BeanUtils.instantiateClass(listenerClass));
|
||||||
|
@ -201,53 +259,6 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot
|
||||||
return listeners;
|
return listeners;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the default {@link TestExecutionListener} classes for this bootstrapper.
|
|
||||||
* <p>This method is invoked by {@link #getTestExecutionListeners()} and
|
|
||||||
* delegates to {@link #getDefaultTestExecutionListenerClassNames()} to
|
|
||||||
* retrieve the class names.
|
|
||||||
* <p>If a particular class cannot be loaded, a {@code DEBUG} message will
|
|
||||||
* be logged, but the associated exception will not be rethrown.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected Set<Class<? extends TestExecutionListener>> getDefaultTestExecutionListenerClasses() {
|
|
||||||
Set<Class<? extends TestExecutionListener>> defaultListenerClasses = new LinkedHashSet<>();
|
|
||||||
ClassLoader cl = getClass().getClassLoader();
|
|
||||||
for (String className : getDefaultTestExecutionListenerClassNames()) {
|
|
||||||
try {
|
|
||||||
defaultListenerClasses.add((Class<? extends TestExecutionListener>) ClassUtils.forName(className, cl));
|
|
||||||
}
|
|
||||||
catch (Throwable ex) {
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug("Could not load default TestExecutionListener class [" + className +
|
|
||||||
"]. Specify custom listener classes or make the default listener classes available.", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultListenerClasses;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the names of the default {@link TestExecutionListener} classes for
|
|
||||||
* this bootstrapper.
|
|
||||||
* <p>The default implementation looks up all
|
|
||||||
* {@code org.springframework.test.context.TestExecutionListener} entries
|
|
||||||
* configured in all {@code META-INF/spring.factories} files on the classpath.
|
|
||||||
* <p>This method is invoked by {@link #getDefaultTestExecutionListenerClasses()}.
|
|
||||||
* @return an <em>unmodifiable</em> list of names of default {@code TestExecutionListener}
|
|
||||||
* classes
|
|
||||||
* @see SpringFactoriesLoader#loadFactoryNames
|
|
||||||
*/
|
|
||||||
protected List<String> getDefaultTestExecutionListenerClassNames() {
|
|
||||||
List<String> classNames =
|
|
||||||
SpringFactoriesLoader.loadFactoryNames(TestExecutionListener.class, getClass().getClassLoader());
|
|
||||||
if (logger.isInfoEnabled()) {
|
|
||||||
logger.info(String.format("Loaded default TestExecutionListener class names from location [%s]: %s",
|
|
||||||
SpringFactoriesLoader.FACTORIES_RESOURCE_LOCATION, classNames));
|
|
||||||
}
|
|
||||||
return Collections.unmodifiableList(classNames);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue