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,
char.class, '\0');
private static final boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
/**
* 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");
try {
ReflectionUtils.makeAccessible(ctor);
if (KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
if (KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
return KotlinDelegate.instantiateClass(ctor, args);
}
else {
@ -279,7 +281,7 @@ public abstract class BeanUtils {
*/
public static <T> @Nullable Constructor<T> findPrimaryConstructor(Class<T> clazz) {
Assert.notNull(clazz, "Class must not be null");
if (KotlinDetector.isKotlinType(clazz)) {
if (KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(clazz)) {
return KotlinDelegate.findPrimaryConstructor(clazz);
}
if (clazz.isRecord()) {
@ -659,7 +661,7 @@ public abstract class BeanUtils {
ConstructorProperties cp = ctor.getAnnotation(ConstructorProperties.class);
@Nullable String[] paramNames = (cp != null ? cp.value() : parameterNameDiscoverer.getParameterNames(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());
Assert.state(paramNames.length == parameterCount,
() -> "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 boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
private final GenerationContext generationContext;
@ -161,7 +163,7 @@ public class InstanceSupplierCodeGenerator {
registeredBean.getBeanName(), constructor, registeredBean.getBeanClass());
Class<?> publicType = descriptor.publicType();
if (KotlinDetector.isKotlinType(publicType) && KotlinDelegate.hasConstructorWithOptionalParameter(publicType)) {
if (KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(publicType) && KotlinDelegate.hasConstructorWithOptionalParameter(publicType)) {
return generateCodeForInaccessibleConstructor(descriptor,
hints -> hints.registerType(publicType, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
}

View File

@ -35,8 +35,11 @@ package org.springframework.core;
*/
public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {
private static final boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
public DefaultParameterNameDiscoverer() {
if (KotlinDetector.isKotlinReflectPresent()) {
if (KOTLIN_REFLECT_PRESENT) {
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 boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
private final Executable executable;
@ -396,7 +398,8 @@ public class MethodParameter {
*/
public boolean isOptional() {
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) {
Method method = getMethod();
paramType = (method != null ?
(KotlinDetector.isKotlinType(getContainingClass()) ?
(KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(getContainingClass()) ?
KotlinDelegate.getGenericReturnType(method) : method.getGenericReturnType()) : void.class);
}
else {
@ -512,7 +515,7 @@ public class MethodParameter {
if (method == null) {
return void.class;
}
if (KotlinDetector.isKotlinType(getContainingClass())) {
if (KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(getContainingClass())) {
return KotlinDelegate.getReturnType(method);
}
return method.getReturnType();

View File

@ -77,6 +77,8 @@ public enum Nullness {
*/
NON_NULL;
private static final boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
/**
* Return the nullness of the return type for the given method.
@ -84,7 +86,7 @@ public enum Nullness {
* @return the corresponding nullness
*/
public static Nullness forMethodReturnType(Method method) {
if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
if (KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(method.getDeclaringClass())) {
return KotlinDelegate.forMethodReturnType(method);
}
return (hasNullableAnnotation(method) ? Nullness.NULLABLE :
@ -97,7 +99,7 @@ public enum Nullness {
* @return the corresponding nullness
*/
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
MethodParameter methodParameter = MethodParameter.forParameter(parameter);
return KotlinDelegate.forParameter(methodParameter.getExecutable(), methodParameter.getParameterIndex());
@ -124,7 +126,7 @@ public enum Nullness {
* @return the corresponding nullness
*/
public static Nullness forField(Field field) {
if (KotlinDetector.isKotlinType(field.getDeclaringClass())) {
if (KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(field.getDeclaringClass())) {
return KotlinDelegate.forField(field);
}
return (hasNullableAnnotation(field) ? Nullness.NULLABLE :

View File

@ -360,8 +360,11 @@ public class SpringFactoriesLoader {
*/
static final class FactoryInstantiator<T> {
private static final boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
private final Constructor<T> constructor;
private FactoryInstantiator(Constructor<T> constructor) {
ReflectionUtils.makeAccessible(constructor);
this.constructor = constructor;
@ -369,7 +372,7 @@ public class SpringFactoriesLoader {
T instantiate(@Nullable ArgumentResolver argumentResolver) throws Exception {
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 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 boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
private final boolean allowWrite;
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) {
Class<?> clazz = method.getDeclaringClass();
return KotlinDetector.isKotlinType(clazz) &&
return KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(clazz) &&
KotlinDelegate.isKotlinProperty(method, methodSuffix);
}

View File

@ -72,6 +72,8 @@ import org.springframework.web.method.support.ModelAndViewContainer;
*/
public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
private static final boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
private final @Nullable ConfigurableBeanFactory configurableBeanFactory;
private final @Nullable BeanExpressionContext expressionContext;
@ -103,7 +105,7 @@ public abstract class AbstractNamedValueMethodArgumentResolver implements Handle
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
boolean hasDefaultValue = KotlinDetector.isKotlinType(parameter.getDeclaringClass()) &&
boolean hasDefaultValue = KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(parameter.getDeclaringClass()) &&
KotlinDelegate.hasDefaultValue(nestedParameter);
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 boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
@ -247,7 +249,7 @@ public class InvocableHandlerMethod extends HandlerMethod {
protected @Nullable Object doInvoke(@Nullable Object... args) throws Exception {
Method method = getBridgedMethod();
try {
if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
if (KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(method.getDeclaringClass())) {
if (KotlinDetector.isSuspendingFunction(method)) {
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 boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
private final HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
@ -197,12 +199,17 @@ public class InvocableHandlerMethod extends HandlerMethod {
}
}
Object value;
boolean isSuspendingFunction;
Method method = getBridgedMethod();
boolean isSuspendingFunction = KotlinDetector.isSuspendingFunction(method);
try {
value = (KotlinDetector.isKotlinType(method.getDeclaringClass()) ?
KotlinDelegate.invokeFunction(method, getBean(), args, isSuspendingFunction, exchange) :
method.invoke(getBean(), args));
if (KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(method.getDeclaringClass())) {
isSuspendingFunction = KotlinDetector.isSuspendingFunction(method);
value = KotlinDelegate.invokeFunction(method, getBean(), args, isSuspendingFunction, exchange);
}
else {
isSuspendingFunction = false;
value = method.invoke(getBean(), args);
}
}
catch (IllegalArgumentException ex) {
assertTargetBean(getBridgedMethod(), getBean(), args);

View File

@ -69,6 +69,8 @@ import org.springframework.web.server.ServerWebInputException;
*/
public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodArgumentResolverSupport {
private static final boolean KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
private final @Nullable ConfigurableBeanFactory configurableBeanFactory;
private final @Nullable BeanExpressionContext expressionContext;
@ -222,7 +224,7 @@ public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodAr
return Mono.fromSupplier(() -> {
Object value = null;
boolean hasDefaultValue = KotlinDetector.isKotlinType(parameter.getDeclaringClass()) &&
boolean hasDefaultValue = KOTLIN_REFLECT_PRESENT && KotlinDetector.isKotlinType(parameter.getDeclaringClass()) &&
KotlinDelegate.hasDefaultValue(parameter);
if (namedValueInfo.defaultValue != null) {
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.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.KotlinDetector;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.Formatter;
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 KOTLIN_REFLECT_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_2_PRESENT = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
KOTLIN_REFLECT_PRESENT = KotlinDetector.isKotlinReflectPresent();
KOTLIN_SERIALIZATION_PRESENT = ClassUtils.isPresent("kotlinx.serialization.Serializable", classLoader);
}
@ -659,7 +663,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
requestBodyAdvices.add(new JsonViewRequestBodyAdvice());
responseBodyAdvices.add(new JsonViewResponseBodyAdvice());
}
if (KOTLIN_SERIALIZATION_PRESENT) {
if (KOTLIN_REFLECT_PRESENT && KOTLIN_SERIALIZATION_PRESENT) {
requestBodyAdvices.add(new KotlinRequestBodyAdvice());
responseBodyAdvices.add(new KotlinResponseBodyAdvice());
}