Reinstantiate checks for kotlin-reflect

This commit reinstantiates checks for kotlin-reflect (via static final
fields for faster Java code paths and better native code removal) which
were removed as part of gh-34275, which did not consider the
increasingly popular use cases where kotlin-stdlib is present in the
classpath as a transitive dependency in Java applications.

Closes gh-35511
This commit is contained in:
Sébastien Deleuze 2025-09-22 18:28:20 +02:00
parent 7635ac38f6
commit c79e4e230b
12 changed files with 56 additions and 21 deletions

View File

@ -88,6 +88,8 @@ public abstract class BeanUtils {
double.class, 0D, double.class, 0D,
char.class, '\0'); char.class, '\0');
private static final boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
/** /**
* Convenience method to instantiate a class using its no-arg constructor. * Convenience method to instantiate a class using its no-arg constructor.
@ -186,7 +188,7 @@ public abstract class BeanUtils {
Assert.notNull(ctor, "Constructor must not be null"); Assert.notNull(ctor, "Constructor must not be null");
try { try {
ReflectionUtils.makeAccessible(ctor); ReflectionUtils.makeAccessible(ctor);
if (KotlinDetector.isKotlinType(ctor.getDeclaringClass())) { if (KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
return KotlinDelegate.instantiateClass(ctor, args); return KotlinDelegate.instantiateClass(ctor, args);
} }
else { else {
@ -279,7 +281,7 @@ public abstract class BeanUtils {
*/ */
public static <T> @Nullable Constructor<T> findPrimaryConstructor(Class<T> clazz) { public static <T> @Nullable Constructor<T> findPrimaryConstructor(Class<T> clazz) {
Assert.notNull(clazz, "Class must not be null"); Assert.notNull(clazz, "Class must not be null");
if (KotlinDetector.isKotlinType(clazz)) { if (KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(clazz)) {
return KotlinDelegate.findPrimaryConstructor(clazz); return KotlinDelegate.findPrimaryConstructor(clazz);
} }
if (clazz.isRecord()) { if (clazz.isRecord()) {
@ -659,7 +661,7 @@ public abstract class BeanUtils {
ConstructorProperties cp = ctor.getAnnotation(ConstructorProperties.class); ConstructorProperties cp = ctor.getAnnotation(ConstructorProperties.class);
@Nullable String[] paramNames = (cp != null ? cp.value() : parameterNameDiscoverer.getParameterNames(ctor)); @Nullable String[] paramNames = (cp != null ? cp.value() : parameterNameDiscoverer.getParameterNames(ctor));
Assert.state(paramNames != null, () -> "Cannot resolve parameter names for constructor " + ctor); Assert.state(paramNames != null, () -> "Cannot resolve parameter names for constructor " + ctor);
int parameterCount = (KotlinDetector.isKotlinReflectPresent() && KotlinDelegate.hasDefaultConstructorMarker(ctor) ? int parameterCount = (KOTLIN_REFLECT_PRESENT && KotlinDelegate.hasDefaultConstructorMarker(ctor) ?
ctor.getParameterCount() - 1 : ctor.getParameterCount()); ctor.getParameterCount() - 1 : ctor.getParameterCount());
Assert.state(paramNames.length == parameterCount, Assert.state(paramNames.length == parameterCount,
() -> "Invalid number of parameter names: " + paramNames.length + " for constructor " + ctor); () -> "Invalid number of parameter names: " + paramNames.length + " for constructor " + ctor);

View File

@ -88,6 +88,8 @@ public class InstanceSupplierCodeGenerator {
private static final CodeBlock NO_ARGS = CodeBlock.of(""); private static final CodeBlock NO_ARGS = CodeBlock.of("");
private static final boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
private final GenerationContext generationContext; private final GenerationContext generationContext;
@ -161,7 +163,7 @@ public class InstanceSupplierCodeGenerator {
registeredBean.getBeanName(), constructor, registeredBean.getBeanClass()); registeredBean.getBeanName(), constructor, registeredBean.getBeanClass());
Class<?> publicType = descriptor.publicType(); Class<?> publicType = descriptor.publicType();
if (KotlinDetector.isKotlinType(publicType) && KotlinDelegate.hasConstructorWithOptionalParameter(publicType)) { if (KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(publicType) && KotlinDelegate.hasConstructorWithOptionalParameter(publicType)) {
return generateCodeForInaccessibleConstructor(descriptor, return generateCodeForInaccessibleConstructor(descriptor,
hints -> hints.registerType(publicType, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)); hints -> hints.registerType(publicType, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
} }

View File

@ -35,8 +35,11 @@ package org.springframework.core;
*/ */
public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer { public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {
private static final boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
public DefaultParameterNameDiscoverer() { public DefaultParameterNameDiscoverer() {
if (KotlinDetector.isKotlinReflectPresent()) { if (KOTLIN_REFLECT_PRESENT) {
addDiscoverer(new KotlinReflectionParameterNameDiscoverer()); addDiscoverer(new KotlinReflectionParameterNameDiscoverer());
} }

View File

@ -66,6 +66,8 @@ public class MethodParameter {
private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0];
private static final boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
private final Executable executable; private final Executable executable;
@ -396,7 +398,8 @@ public class MethodParameter {
*/ */
public boolean isOptional() { public boolean isOptional() {
return (getParameterType() == Optional.class || Nullness.forMethodParameter(this) == Nullness.NULLABLE || return (getParameterType() == Optional.class || Nullness.forMethodParameter(this) == Nullness.NULLABLE ||
(KotlinDetector.isKotlinType(getContainingClass()) && KotlinDelegate.isOptional(this))); (KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(getContainingClass()) &&
KotlinDelegate.isOptional(this)));
} }
/** /**
@ -484,7 +487,7 @@ public class MethodParameter {
if (this.parameterIndex < 0) { if (this.parameterIndex < 0) {
Method method = getMethod(); Method method = getMethod();
paramType = (method != null ? paramType = (method != null ?
(KotlinDetector.isKotlinType(getContainingClass()) ? (KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(getContainingClass()) ?
KotlinDelegate.getGenericReturnType(method) : method.getGenericReturnType()) : void.class); KotlinDelegate.getGenericReturnType(method) : method.getGenericReturnType()) : void.class);
} }
else { else {
@ -512,7 +515,7 @@ public class MethodParameter {
if (method == null) { if (method == null) {
return void.class; return void.class;
} }
if (KotlinDetector.isKotlinType(getContainingClass())) { if (KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(getContainingClass())) {
return KotlinDelegate.getReturnType(method); return KotlinDelegate.getReturnType(method);
} }
return method.getReturnType(); return method.getReturnType();

View File

@ -77,6 +77,8 @@ public enum Nullness {
*/ */
NON_NULL; NON_NULL;
private static final boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
/** /**
* Return the nullness of the return type for the given method. * Return the nullness of the return type for the given method.
@ -84,7 +86,7 @@ public enum Nullness {
* @return the corresponding nullness * @return the corresponding nullness
*/ */
public static Nullness forMethodReturnType(Method method) { public static Nullness forMethodReturnType(Method method) {
if (KotlinDetector.isKotlinType(method.getDeclaringClass())) { if (KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(method.getDeclaringClass())) {
return KotlinDelegate.forMethodReturnType(method); return KotlinDelegate.forMethodReturnType(method);
} }
return (hasNullableAnnotation(method) ? Nullness.NULLABLE : return (hasNullableAnnotation(method) ? Nullness.NULLABLE :
@ -97,7 +99,7 @@ public enum Nullness {
* @return the corresponding nullness * @return the corresponding nullness
*/ */
public static Nullness forParameter(Parameter parameter) { public static Nullness forParameter(Parameter parameter) {
if (KotlinDetector.isKotlinType(parameter.getDeclaringExecutable().getDeclaringClass())) { if (KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(parameter.getDeclaringExecutable().getDeclaringClass())) {
// TODO Optimize when kotlin-reflect provide a more direct Parameter to KParameter resolution // TODO Optimize when kotlin-reflect provide a more direct Parameter to KParameter resolution
MethodParameter methodParameter = MethodParameter.forParameter(parameter); MethodParameter methodParameter = MethodParameter.forParameter(parameter);
return KotlinDelegate.forParameter(methodParameter.getExecutable(), methodParameter.getParameterIndex()); return KotlinDelegate.forParameter(methodParameter.getExecutable(), methodParameter.getParameterIndex());
@ -124,7 +126,7 @@ public enum Nullness {
* @return the corresponding nullness * @return the corresponding nullness
*/ */
public static Nullness forField(Field field) { public static Nullness forField(Field field) {
if (KotlinDetector.isKotlinType(field.getDeclaringClass())) { if (KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(field.getDeclaringClass())) {
return KotlinDelegate.forField(field); return KotlinDelegate.forField(field);
} }
return (hasNullableAnnotation(field) ? Nullness.NULLABLE : return (hasNullableAnnotation(field) ? Nullness.NULLABLE :

View File

@ -360,8 +360,11 @@ public class SpringFactoriesLoader {
*/ */
static final class FactoryInstantiator<T> { static final class FactoryInstantiator<T> {
private static final boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
private final Constructor<T> constructor; private final Constructor<T> constructor;
private FactoryInstantiator(Constructor<T> constructor) { private FactoryInstantiator(Constructor<T> constructor) {
ReflectionUtils.makeAccessible(constructor); ReflectionUtils.makeAccessible(constructor);
this.constructor = constructor; this.constructor = constructor;
@ -369,7 +372,7 @@ public class SpringFactoriesLoader {
T instantiate(@Nullable ArgumentResolver argumentResolver) throws Exception { T instantiate(@Nullable ArgumentResolver argumentResolver) throws Exception {
Object[] args = resolveArgs(argumentResolver); Object[] args = resolveArgs(argumentResolver);
if (KotlinDetector.isKotlinType(this.constructor.getDeclaringClass())) { if (KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(this.constructor.getDeclaringClass())) {
return KotlinDelegate.instantiate(this.constructor, args); return KotlinDelegate.instantiate(this.constructor, args);
} }
return this.constructor.newInstance(args); return this.constructor.newInstance(args);

View File

@ -75,6 +75,9 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
private static final Set<Class<?>> BOOLEAN_TYPES = Set.of(Boolean.class, boolean.class); private static final Set<Class<?>> BOOLEAN_TYPES = Set.of(Boolean.class, boolean.class);
private static final boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
private final boolean allowWrite; private final boolean allowWrite;
private final Map<PropertyCacheKey, InvokerPair> readerCache = new ConcurrentHashMap<>(64); private final Map<PropertyCacheKey, InvokerPair> readerCache = new ConcurrentHashMap<>(64);
@ -558,7 +561,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
private static boolean isKotlinProperty(Method method, String methodSuffix) { private static boolean isKotlinProperty(Method method, String methodSuffix) {
Class<?> clazz = method.getDeclaringClass(); Class<?> clazz = method.getDeclaringClass();
return KotlinDetector.isKotlinType(clazz) && return KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(clazz) &&
KotlinDelegate.isKotlinProperty(method, methodSuffix); KotlinDelegate.isKotlinProperty(method, methodSuffix);
} }

View File

@ -72,6 +72,8 @@ import org.springframework.web.method.support.ModelAndViewContainer;
*/ */
public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver { public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
private static final boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
private final @Nullable ConfigurableBeanFactory configurableBeanFactory; private final @Nullable ConfigurableBeanFactory configurableBeanFactory;
private final @Nullable BeanExpressionContext expressionContext; private final @Nullable BeanExpressionContext expressionContext;
@ -103,7 +105,7 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter); NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional(); MethodParameter nestedParameter = parameter.nestedIfOptional();
boolean hasDefaultValue = KotlinDetector.isKotlinType(parameter.getDeclaringClass()) && boolean hasDefaultValue = KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(parameter.getDeclaringClass()) &&
KotlinDelegate.hasDefaultValue(nestedParameter); KotlinDelegate.hasDefaultValue(nestedParameter);
Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name); Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);

View File

@ -66,6 +66,8 @@ public class InvocableHandlerMethod extends HandlerMethod {
private static final Class<?>[] EMPTY_GROUPS = new Class<?>[0]; private static final Class<?>[] EMPTY_GROUPS = new Class<?>[0];
private static final boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite(); private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
@ -247,7 +249,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
protected @Nullable Object doInvoke(@Nullable Object... args) throws Exception { protected @Nullable Object doInvoke(@Nullable Object... args) throws Exception {
Method method = getBridgedMethod(); Method method = getBridgedMethod();
try { try {
if (KotlinDetector.isKotlinType(method.getDeclaringClass())) { if (KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(method.getDeclaringClass())) {
if (KotlinDetector.isSuspendingFunction(method)) { if (KotlinDetector.isSuspendingFunction(method)) {
return invokeSuspendingFunction(method, getBean(), args); return invokeSuspendingFunction(method, getBean(), args);
} }

View File

@ -85,6 +85,8 @@ public class InvocableHandlerMethod extends HandlerMethod {
private static final Object NO_ARG_VALUE = new Object(); private static final Object NO_ARG_VALUE = new Object();
private static final boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
private final HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite(); private final HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
@ -197,12 +199,17 @@ public class InvocableHandlerMethod extends HandlerMethod {
} }
} }
Object value; Object value;
boolean isSuspendingFunction;
Method method = getBridgedMethod(); Method method = getBridgedMethod();
boolean isSuspendingFunction = KotlinDetector.isSuspendingFunction(method);
try { try {
value = (KotlinDetector.isKotlinType(method.getDeclaringClass()) ? if (KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(method.getDeclaringClass())) {
KotlinDelegate.invokeFunction(method, getBean(), args, isSuspendingFunction, exchange) : isSuspendingFunction = KotlinDetector.isSuspendingFunction(method);
method.invoke(getBean(), args)); value = KotlinDelegate.invokeFunction(method, getBean(), args, isSuspendingFunction, exchange);
}
else {
isSuspendingFunction = false;
value = method.invoke(getBean(), args);
}
} }
catch (IllegalArgumentException ex) { catch (IllegalArgumentException ex) {
assertTargetBean(getBridgedMethod(), getBean(), args); assertTargetBean(getBridgedMethod(), getBean(), args);

View File

@ -69,6 +69,8 @@ import org.springframework.web.server.ServerWebInputException;
*/ */
public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodArgumentResolverSupport { public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodArgumentResolverSupport {
private static final boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
private final @Nullable ConfigurableBeanFactory configurableBeanFactory; private final @Nullable ConfigurableBeanFactory configurableBeanFactory;
private final @Nullable BeanExpressionContext expressionContext; private final @Nullable BeanExpressionContext expressionContext;
@ -222,7 +224,7 @@ public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodAr
return Mono.fromSupplier(() -> { return Mono.fromSupplier(() -> {
Object value = null; Object value = null;
boolean hasDefaultValue = KotlinDetector.isKotlinType(parameter.getDeclaringClass()) && boolean hasDefaultValue = KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(parameter.getDeclaringClass()) &&
KotlinDelegate.hasDefaultValue(parameter); KotlinDelegate.hasDefaultValue(parameter);
if (namedValueInfo.defaultValue != null) { if (namedValueInfo.defaultValue != null) {
value = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue); value = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);

View File

@ -36,6 +36,7 @@ import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.core.KotlinDetector;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import org.springframework.format.Formatter; import org.springframework.format.Formatter;
import org.springframework.format.FormatterRegistry; import org.springframework.format.FormatterRegistry;
@ -184,6 +185,8 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
private static final boolean JACKSON_2_PRESENT; private static final boolean JACKSON_2_PRESENT;
private static final boolean KOTLIN_REFLECT_PRESENT;
private static final boolean KOTLIN_SERIALIZATION_PRESENT; private static final boolean KOTLIN_SERIALIZATION_PRESENT;
@ -192,6 +195,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
JACKSON_PRESENT = ClassUtils.isPresent("tools.jackson.databind.ObjectMapper", classLoader); JACKSON_PRESENT = ClassUtils.isPresent("tools.jackson.databind.ObjectMapper", classLoader);
JACKSON_2_PRESENT = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && JACKSON_2_PRESENT = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
KOTLIN_SERIALIZATION_PRESENT = ClassUtils.isPresent("kotlinx.serialization.Serializable", classLoader); KOTLIN_SERIALIZATION_PRESENT = ClassUtils.isPresent("kotlinx.serialization.Serializable", classLoader);
} }
@ -659,7 +663,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
requestBodyAdvices.add(new JsonViewRequestBodyAdvice()); requestBodyAdvices.add(new JsonViewRequestBodyAdvice());
responseBodyAdvices.add(new JsonViewResponseBodyAdvice()); responseBodyAdvices.add(new JsonViewResponseBodyAdvice());
} }
if (KOTLIN_SERIALIZATION_PRESENT) { if (KOTLIN_REFLECT_PRESENT && KOTLIN_SERIALIZATION_PRESENT) {
requestBodyAdvices.add(new KotlinRequestBodyAdvice()); requestBodyAdvices.add(new KotlinRequestBodyAdvice());
responseBodyAdvices.add(new KotlinResponseBodyAdvice()); responseBodyAdvices.add(new KotlinResponseBodyAdvice());
} }