Leverage kotlin-reflect to determine parameter names
This is especially useful to determine interface parameter names without requiring Java 8 -parameters compiler flag. Issue: SPR-15541
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,22 +16,35 @@
|
||||
|
||||
package org.springframework.core;
|
||||
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Default implementation of the {@link ParameterNameDiscoverer} strategy interface,
|
||||
* using the Java 8 standard reflection mechanism (if available), and falling back
|
||||
* to the ASM-based {@link LocalVariableTableParameterNameDiscoverer} for checking
|
||||
* debug information in the class file.
|
||||
*
|
||||
* <p>If Kotlin is present, {@link KotlinReflectionParameterNameDiscoverer} is added first
|
||||
* in the list and used for Kotlin classes and interfaces.
|
||||
*
|
||||
* <p>Further discoverers may be added through {@link #addDiscoverer(ParameterNameDiscoverer)}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Sebastien Deleuze
|
||||
* @since 4.0
|
||||
* @see StandardReflectionParameterNameDiscoverer
|
||||
* @see LocalVariableTableParameterNameDiscoverer
|
||||
* @see KotlinReflectionParameterNameDiscoverer
|
||||
*/
|
||||
public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {
|
||||
|
||||
private static final boolean kotlinPresent =
|
||||
ClassUtils.isPresent("kotlin.Unit", DefaultParameterNameDiscoverer.class.getClassLoader());
|
||||
|
||||
public DefaultParameterNameDiscoverer() {
|
||||
if (kotlinPresent) {
|
||||
addDiscoverer(new KotlinReflectionParameterNameDiscoverer());
|
||||
}
|
||||
addDiscoverer(new StandardReflectionParameterNameDiscoverer());
|
||||
addDiscoverer(new LocalVariableTableParameterNameDiscoverer());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import kotlin.reflect.KFunction;
|
||||
import kotlin.reflect.KParameter;
|
||||
import kotlin.reflect.jvm.ReflectJvmMapping;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* {@link ParameterNameDiscoverer} implementation which uses Kotlin's reflection facilities
|
||||
* for introspecting parameter names.
|
||||
*
|
||||
* Compared to {@link StandardReflectionParameterNameDiscoverer}, it allows in addition to
|
||||
* determine interface parameter names without requiring Java 8 -parameters compiler flag.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 5.0
|
||||
*/
|
||||
public class KotlinReflectionParameterNameDiscoverer implements ParameterNameDiscoverer {
|
||||
|
||||
@Nullable
|
||||
private static final Class<?> kotlinMetadata;
|
||||
|
||||
static {
|
||||
Class<?> metadata;
|
||||
try {
|
||||
metadata = ClassUtils.forName("kotlin.Metadata", KotlinReflectionParameterNameDiscoverer.class.getClassLoader());
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
// Kotlin API not available - no special support for Kotlin class instantiation
|
||||
metadata = null;
|
||||
}
|
||||
kotlinMetadata = metadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String[] getParameterNames(Method method) {
|
||||
if (!useKotlinSupport(method.getDeclaringClass())) {
|
||||
return null;
|
||||
}
|
||||
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(method);
|
||||
return (function != null ? getParameterNames(function.getParameters()) : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String[] getParameterNames(Constructor<?> ctor) {
|
||||
if (!useKotlinSupport(ctor.getDeclaringClass())) {
|
||||
return null;
|
||||
}
|
||||
KFunction<?> function = ReflectJvmMapping.getKotlinFunction(ctor);
|
||||
return (function != null ? getParameterNames(function.getParameters()) : null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String[] getParameterNames(List<KParameter> parameters) {
|
||||
List<KParameter> filteredParameters = parameters
|
||||
.stream()
|
||||
.filter(p -> KParameter.Kind.VALUE.equals(p.getKind()))
|
||||
.collect(Collectors.toList());
|
||||
String[] parameterNames = new String[filteredParameters.size()];
|
||||
for (int i = 0; i < filteredParameters.size(); i++) {
|
||||
String name = filteredParameters.get(i).getName();
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
parameterNames[i] = name;
|
||||
}
|
||||
return parameterNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if Kotlin is present and if the specified class is a Kotlin one.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static boolean useKotlinSupport(Class<?> clazz) {
|
||||
return (kotlinMetadata != null &&
|
||||
clazz.getDeclaredAnnotation((Class<? extends Annotation>) kotlinMetadata) != null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.core
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.hamcrest.CoreMatchers.`is`
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.springframework.util.ReflectionUtils
|
||||
|
||||
/**
|
||||
* Tests for KotlinReflectionParameterNameDiscoverer
|
||||
*/
|
||||
class KotlinReflectionParameterNameDiscovererTests {
|
||||
|
||||
private val parameterNameDiscoverer = KotlinReflectionParameterNameDiscoverer()
|
||||
|
||||
@Test
|
||||
fun getParameterNamesOnInterface() {
|
||||
val method = ReflectionUtils.findMethod(MessageService::class.java,"sendMessage", String::class.java)!!
|
||||
val actualParams = parameterNameDiscoverer.getParameterNames(method)
|
||||
assertThat(actualParams, `is`(arrayOf("message")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getParameterNamesOnClass() {
|
||||
val method = ReflectionUtils.findMethod(MessageServiceImpl::class.java,"sendMessage", String::class.java)!!
|
||||
val actualParams = parameterNameDiscoverer.getParameterNames(method)
|
||||
assertThat(actualParams, `is`(arrayOf("message")))
|
||||
}
|
||||
|
||||
interface MessageService {
|
||||
fun sendMessage(message: String)
|
||||
}
|
||||
|
||||
class MessageServiceImpl {
|
||||
fun sendMessage(message: String) = message
|
||||
}
|
||||
}
|
||||
@@ -115,12 +115,16 @@ Other libraries like Reactor or Spring Data leverage these annotations to provid
|
||||
null-safe APIs for Kotlin developers.
|
||||
====
|
||||
|
||||
== Classes
|
||||
== Classes & Interfaces
|
||||
|
||||
Spring Framework 5 now supports various Kotlin constructs like instantiating Kotlin classes
|
||||
via primary constructors, immutable classes data binding and function optional parameters
|
||||
with default values.
|
||||
|
||||
Kotlin parameter names are recognized via a dedicated `KotlinReflectionParameterNameDiscoverer`
|
||||
which allows to find interface method parameter names without requiring Java 8 `-parameters`
|
||||
compiler flag.
|
||||
|
||||
https://github.com/FasterXML/jackson-module-kotlin[Jackson Kotlin module] which is required
|
||||
for serializing / deserializing JSON data is automatically registered when present in the
|
||||
classpath, and will log a warning message if Jackson + Kotlin are detected without Jackson
|
||||
@@ -509,7 +513,6 @@ Here is a list of pending issues related to Spring + Kotlin support.
|
||||
|
||||
===== Spring Framework
|
||||
|
||||
* https://jira.spring.io/browse/SPR-15541[Leveraging kotlin-reflect to determine interface method parameters]
|
||||
* https://jira.spring.io/browse/SPR-15413[Add support for Kotlin coroutines]
|
||||
|
||||
===== Spring Boot
|
||||
|
||||
Reference in New Issue
Block a user