Browse Source

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
pull/1507/merge
Sebastien Deleuze 7 years ago
parent
commit
5ae35f606c
  1. 15
      spring-core/src/main/java/org/springframework/core/DefaultParameterNameDiscoverer.java
  2. 105
      spring-core/src/main/java/org/springframework/core/KotlinReflectionParameterNameDiscoverer.java
  3. 52
      spring-core/src/test/kotlin/org/springframework/core/KotlinReflectionParameterNameDiscovererTests.kt
  4. 7
      src/docs/asciidoc/kotlin.adoc

15
spring-core/src/main/java/org/springframework/core/DefaultParameterNameDiscoverer.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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());
}

105
spring-core/src/main/java/org/springframework/core/KotlinReflectionParameterNameDiscoverer.java

@ -0,0 +1,105 @@ @@ -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);
}
}

52
spring-core/src/test/kotlin/org/springframework/core/KotlinReflectionParameterNameDiscovererTests.kt

@ -0,0 +1,52 @@ @@ -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
}
}

7
src/docs/asciidoc/kotlin.adoc

@ -115,12 +115,16 @@ Other libraries like Reactor or Spring Data leverage these annotations to provid @@ -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. @@ -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

Loading…
Cancel
Save