diff --git a/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java b/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java index 3d3cc32a7ffb9a4e607a9ba16975b200de30bcb8..1a8d3cfd82f6366c38e557ae80afafa84d7eb963 100644 --- a/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/BeanUtils.java @@ -98,12 +98,18 @@ public abstract class BeanUtils { } /** - * Instantiate a class using its no-arg constructor. + * Instantiate a class using its 'primary' constructor (for Kotlin classes, + * potentially having default arguments declared) or its default constructor + * (for regular Java classes, expecting a standard no-arg setup). *

Note that this method tries to set the constructor accessible * if given a non-accessible (that is, non-public) constructor. - * @param clazz class to instantiate + * @param clazz the class to instantiate * @return the new instance - * @throws BeanInstantiationException if the bean cannot be instantiated + * @throws BeanInstantiationException if the bean cannot be instantiated. + * The cause may notably indicate a {@link NoSuchMethodException} if no + * primary/default constructor was found - or an exception thrown from + * the constructor invocation attempt, including a runtime-generated + * {@link NoClassDefFoundError} in case of an unresolvable dependency. * @see Constructor#newInstance */ public static T instantiateClass(Class clazz) throws BeanInstantiationException { @@ -113,10 +119,7 @@ public abstract class BeanUtils { } try { Constructor ctor = (KotlinDetector.isKotlinType(clazz) ? - KotlinDelegate.findPrimaryConstructor(clazz) : clazz.getDeclaredConstructor()); - if (ctor == null) { - throw new BeanInstantiationException(clazz, "No default constructor found"); - } + KotlinDelegate.getPrimaryConstructor(clazz) : clazz.getDeclaredConstructor()); return instantiateClass(ctor); } catch (NoSuchMethodException ex) { @@ -693,10 +696,26 @@ public abstract class BeanUtils { private static class KotlinDelegate { /** - * Return the Java constructor corresponding to the Kotlin primary constructor if any. + * Determine the Java constructor corresponding to the Kotlin primary constructor. + * @param clazz the {@link Class} of the Kotlin class + * @throws NoSuchMethodException if no such constructor found + * @since 5.0.3 + * @see #findPrimaryConstructor + * @see Class#getDeclaredConstructor + */ + public static Constructor getPrimaryConstructor(Class clazz) throws NoSuchMethodException { + Constructor ctor = findPrimaryConstructor(clazz); + if (ctor == null) { + throw new NoSuchMethodException(); + } + return ctor; + } + + /** + * Retrieve the Java constructor corresponding to the Kotlin primary constructor, if any. * @param clazz the {@link Class} of the Kotlin class * @see - * http://kotlinlang.org/docs/reference/classes.html#constructors + * http://kotlinlang.org/docs/reference/classes.html#constructors */ @Nullable public static Constructor findPrimaryConstructor(Class clazz) { @@ -706,8 +725,10 @@ public abstract class BeanUtils { return null; } Constructor constructor = ReflectJvmMapping.getJavaConstructor(primaryCtor); - Assert.notNull(constructor, - () -> "Failed to find Java constructor for Kotlin primary constructor: " + clazz.getName()); + if (constructor == null) { + throw new IllegalStateException( + "Failed to find Java constructor for Kotlin primary constructor: " + clazz.getName()); + } return constructor; } catch (UnsupportedOperationException ex) { @@ -718,7 +739,8 @@ public abstract class BeanUtils { /** * Instantiate a Kotlin class using the provided constructor. * @param ctor the constructor of the Kotlin class to instantiate - * @param args the constructor arguments to apply (use null for unspecified parameter if needed) + * @param args the constructor arguments to apply + * (use {@code null} for unspecified parameter if needed) */ public static T instantiateClass(Constructor ctor, Object... args) throws IllegalAccessException, InvocationTargetException, InstantiationException { 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 57f18f4d846ba490c1a5901a420a92c5d8f31b04..e096e216e360fd3e0259183945b46aca353933f3 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 @@ -28,6 +28,7 @@ import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeanInstantiationException; import org.springframework.beans.BeanUtils; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.AnnotationUtils; @@ -178,18 +179,25 @@ public abstract class AbstractTestContextBootstrapper implements TestContextBoot return listeners; } - private List instantiateListeners(Collection> classesList) { - List listeners = new ArrayList<>(classesList.size()); - for (Class listenerClass : classesList) { + private List instantiateListeners(Collection> classes) { + List listeners = new ArrayList<>(classes.size()); + for (Class listenerClass : classes) { try { listeners.add(BeanUtils.instantiateClass(listenerClass)); } - catch (NoClassDefFoundError err) { - if (logger.isDebugEnabled()) { - logger.debug(String.format("Could not instantiate TestExecutionListener [%s]. " + - "Specify custom listener classes or make the default listener classes " + - "(and their required dependencies) available. Offending class: [%s]", - listenerClass.getName(), err.getMessage())); + catch (BeanInstantiationException ex) { + if (ex.getCause() instanceof NoClassDefFoundError) { + // TestExecutionListener not applicable due to a missing dependency + if (logger.isDebugEnabled()) { + logger.debug(String.format( + "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]", + listenerClass.getName(), ex.getCause().getMessage())); + } + } + else { + throw ex; } } }