DATACMNS-1508 - Address review comments.

Added ClassUtils.ifPresent(…) to conditionally call back a Consumer<Class> if a class is available from the given ClassLoader. Extract isSuspend(…) method into KotlinReflectionUtils. Deprecate Kotlin-related methods in our ReflectionUtils as parts are available from Spring Framework directly.

Rename CoCrudRepository to CoroutineCrudRepository and CoroutineSortingRepository. Add tests for KotlinReflectionUtils to test calls without Kotlin dependencies.

Original pull request: #415.
This commit is contained in:
Mark Paluch
2020-01-10 10:55:35 +01:00
parent c945fa4f9a
commit 93c708379e
17 changed files with 425 additions and 254 deletions

View File

@@ -41,6 +41,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.core.KotlinDetector;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
@@ -57,6 +58,7 @@ import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.spel.EvaluationContextProvider;
import org.springframework.data.spel.ExtensionAwareEvaluationContextProvider;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.KotlinReflectionUtils;
import org.springframework.data.util.Optionals;
import org.springframework.data.util.Streamable;
import org.springframework.data.util.TypeInformation;
@@ -477,8 +479,7 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
return false;
}
return !org.springframework.data.util.ReflectionUtils.isKotlinClass(type.getType())
|| org.springframework.data.util.ReflectionUtils.isSupportedKotlinClass(type.getType());
return !KotlinDetector.isKotlinType(type.getType()) || KotlinReflectionUtils.isSupportedKotlinClass(type.getType());
}
/**

View File

@@ -25,6 +25,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.core.KotlinDetector;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
@@ -76,7 +77,7 @@ class BeanWrapper<T> implements PersistentPropertyAccessor<T> {
return;
}
if (org.springframework.data.util.ReflectionUtils.isKotlinClass(property.getOwner().getType())) {
if (KotlinDetector.isKotlinType(property.getOwner().getType())) {
this.bean = (T) KotlinCopyUtil.setProperty(property, bean, value);
return;

View File

@@ -45,6 +45,7 @@ import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
import org.springframework.asm.Type;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.core.KotlinDetector;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
@@ -1444,7 +1445,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert
Class<?> type = property.getOwner().getType();
if (isAccessible(type) && org.springframework.data.util.ReflectionUtils.isKotlinClass(type)) {
if (isAccessible(type) && KotlinDetector.isKotlinType(type)) {
return KotlinCopyMethod.findCopyMethod(type).filter(it -> it.supportsProperty(property)).isPresent();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019 the original author or authors.
* Copyright 2019-2020 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.
@@ -31,7 +31,7 @@ import org.springframework.lang.Nullable;
/**
* Metadata for a implementation {@link Method} invocation. This value object encapsulates whether the called and the
* backing method are regular methods or suspendable Kotlin coroutines methods. It also allows invocation of suspendable
* backing method are regular methods or suspendable Kotlin coroutines methods. It also allows invocation of suspended
* methods by backing the invocation using methods returning reactive types.
*
* @author Mark Paluch
@@ -52,10 +52,10 @@ class ImplementationInvocationMetadata {
return;
}
KFunction<?> declaredFunction = ReflectionUtils.isKotlinClass(declaredMethod.getDeclaringClass())
KFunction<?> declaredFunction = KotlinDetector.isKotlinType(declaredMethod.getDeclaringClass())
? KotlinReflectionUtils.findKotlinFunction(declaredMethod)
: null;
KFunction<?> baseClassFunction = ReflectionUtils.isKotlinClass(baseClassMethod.getDeclaringClass())
KFunction<?> baseClassFunction = KotlinDetector.isKotlinType(baseClassMethod.getDeclaringClass())
? KotlinReflectionUtils.findKotlinFunction(baseClassMethod)
: null;
@@ -68,11 +68,13 @@ class ImplementationInvocationMetadata {
@Nullable
public Object invoke(Method methodToCall, Object instance, Object[] args) throws Throwable {
if (suspendedDeclaredMethod && !suspendedBaseClassMethod && reactiveBaseClassMethod) {
return invokeReactiveToSuspend(methodToCall, instance, args);
}
return shouldAdaptReactiveToSuspended() ? invokeReactiveToSuspend(methodToCall, instance, args)
: methodToCall.invoke(instance, args);
return methodToCall.invoke(instance, args);
}
private boolean shouldAdaptReactiveToSuspended() {
return suspendedDeclaredMethod && !suspendedBaseClassMethod && reactiveBaseClassMethod;
}
@Nullable
@@ -80,16 +82,17 @@ class ImplementationInvocationMetadata {
private Object invokeReactiveToSuspend(Method methodToCall, Object instance, Object[] args)
throws ReflectiveOperationException {
/*
* Kotlin suspended functions are invoked with a synthetic Continuation parameter that keeps track of the Coroutine context.
* We're invoking a method without Continuation as we expect the method to return any sort of reactive type,
* therefore we need to strip the Continuation parameter.
*/
Object[] invocationArguments = new Object[args.length - 1];
System.arraycopy(args, 0, invocationArguments, 0, invocationArguments.length);
Object result = methodToCall.invoke(instance, invocationArguments);
Publisher<?> publisher;
if (result instanceof Publisher) {
publisher = (Publisher<?>) result;
} else {
publisher = ReactiveWrapperConverters.toWrapper(result, Publisher.class);
}
Publisher<?> publisher = result instanceof Publisher ? (Publisher<?>) result
: ReactiveWrapperConverters.toWrapper(result, Publisher.class);
return AwaitKt.awaitFirstOrNull(publisher, (Continuation) args[args.length - 1]);
}

View File

@@ -0,0 +1,221 @@
/*
* Copyright 2020 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
*
* https://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.data.repository.core.support;
import kotlin.coroutines.Continuation;
import kotlinx.coroutines.reactive.AwaitKt;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.reactivestreams.Publisher;
import org.springframework.core.KotlinDetector;
import org.springframework.core.ResolvableType;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.util.QueryExecutionConverters;
import org.springframework.data.repository.util.ReactiveWrapperConverters;
import org.springframework.data.repository.util.ReactiveWrappers;
import org.springframework.data.util.KotlinReflectionUtils;
import org.springframework.data.util.Pair;
import org.springframework.lang.Nullable;
import org.springframework.util.ConcurrentReferenceHashMap;
/**
* This {@link MethodInterceptor} intercepts calls to methods of the custom implementation and delegates the to it if
* configured. Furthermore it resolves method calls to finders and triggers execution of them. You can rely on having a
* custom repository implementation instance set if this returns true.
*
* @author Oliver Gierke
* @author Mark Paluch
*/
class QueryExecutorMethodInterceptor implements MethodInterceptor {
private final Map<Method, RepositoryQuery> queries;
private final Map<Method, QueryMethodInvoker> invocationMetadataCache = new ConcurrentReferenceHashMap<>();
private final QueryExecutionResultHandler resultHandler;
private final NamedQueries namedQueries;
private final List<QueryCreationListener<?>> queryPostProcessors;
/**
* Creates a new {@link QueryExecutorMethodInterceptor}. Builds a model of {@link QueryMethod}s to be invoked on
* execution of repository interface methods.
*/
public QueryExecutorMethodInterceptor(RepositoryInformation repositoryInformation,
ProjectionFactory projectionFactory, Optional<QueryLookupStrategy> queryLookupStrategy, NamedQueries namedQueries,
List<QueryCreationListener<?>> queryPostProcessors) {
this.namedQueries = namedQueries;
this.queryPostProcessors = queryPostProcessors;
this.resultHandler = new QueryExecutionResultHandler(RepositoryFactorySupport.CONVERSION_SERVICE);
if (!queryLookupStrategy.isPresent() && repositoryInformation.hasQueryMethods()) {
throw new IllegalStateException("You have defined query method in the repository but "
+ "you don't have any query lookup strategy defined. The "
+ "infrastructure apparently does not support query methods!");
}
this.queries = queryLookupStrategy //
.map(it -> mapMethodsToQuery(repositoryInformation, it, projectionFactory)) //
.orElse(Collections.emptyMap());
}
private Map<Method, RepositoryQuery> mapMethodsToQuery(RepositoryInformation repositoryInformation,
QueryLookupStrategy lookupStrategy, ProjectionFactory projectionFactory) {
return repositoryInformation.getQueryMethods().stream() //
.map(method -> lookupQuery(method, repositoryInformation, lookupStrategy, projectionFactory)) //
.peek(pair -> invokeListeners(pair.getSecond())) //
.collect(Pair.toMap());
}
private Pair<Method, RepositoryQuery> lookupQuery(Method method, RepositoryInformation information,
QueryLookupStrategy strategy, ProjectionFactory projectionFactory) {
return Pair.of(method, strategy.resolveQuery(method, information, projectionFactory, namedQueries));
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void invokeListeners(RepositoryQuery query) {
for (QueryCreationListener listener : queryPostProcessors) {
ResolvableType typeArgument = ResolvableType.forClass(QueryCreationListener.class, listener.getClass())
.getGeneric(0);
if (typeArgument != null && typeArgument.isAssignableFrom(ResolvableType.forClass(query.getClass()))) {
listener.onCreation(query);
}
}
}
/*
* (non-Javadoc)
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
*/
@Override
@Nullable
public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
QueryExecutionConverters.ExecutionAdapter executionAdapter = QueryExecutionConverters //
.getExecutionAdapter(method.getReturnType());
if (executionAdapter == null) {
return resultHandler.postProcessInvocationResult(doInvoke(invocation), method);
}
return executionAdapter //
.apply(() -> resultHandler.postProcessInvocationResult(doInvoke(invocation), method));
}
@Nullable
private Object doInvoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
if (hasQueryFor(method)) {
QueryMethodInvoker invocationMetadata = invocationMetadataCache.get(method);
if (invocationMetadata == null) {
invocationMetadata = new QueryMethodInvoker(method);
invocationMetadataCache.put(method, invocationMetadata);
}
RepositoryQuery repositoryQuery = queries.get(method);
return invocationMetadata.invoke(repositoryQuery, invocation.getArguments());
}
return invocation.proceed();
}
/**
* Returns whether we know of a query to execute for the given {@link Method};
*
* @param method
* @return
*/
private boolean hasQueryFor(Method method) {
return queries.containsKey(method);
}
/**
* Invoker for Query Methods. Considers
*/
static class QueryMethodInvoker {
private final boolean suspendedDeclaredMethod;
private final Class<?> returnedType;
private final boolean returnsReactiveType;
QueryMethodInvoker(Method invokedMethod) {
if (KotlinDetector.isKotlinReflectPresent()) {
this.suspendedDeclaredMethod = KotlinReflectionUtils.isSuspend(invokedMethod);
this.returnedType = this.suspendedDeclaredMethod ? KotlinReflectionUtils.getReturnType(invokedMethod)
: invokedMethod.getReturnType();
} else {
this.suspendedDeclaredMethod = false;
this.returnedType = invokedMethod.getReturnType();
}
this.returnsReactiveType = ReactiveWrappers.supports(returnedType);
}
@Nullable
public Object invoke(RepositoryQuery query, Object[] args) {
return suspendedDeclaredMethod ? invokeReactiveToSuspend(query, args) : query.execute(args);
}
@Nullable
@SuppressWarnings({ "unchecked", "ConstantConditions" })
private Object invokeReactiveToSuspend(RepositoryQuery query, Object[] args) {
/*
* Kotlin suspended functions are invoked with a synthetic Continuation parameter that keeps track of the Coroutine context.
* We're invoking a method without Continuation as we expect the method to return any sort of reactive type,
* therefore we need to strip the Continuation parameter.
*/
Continuation<Object> continuation = (Continuation) args[args.length - 1];
args[args.length - 1] = null;
Object result = query.execute(args);
if (returnsReactiveType) {
return ReactiveWrapperConverters.toWrapper(result, returnedType);
}
Publisher<?> publisher = result instanceof Publisher ? (Publisher<?>) result
: ReactiveWrapperConverters.toWrapper(result, Publisher.class);
return AwaitKt.awaitFirstOrNull(publisher, continuation);
}
}
}

View File

@@ -15,9 +15,6 @@
*/
package org.springframework.data.repository.core.support;
import kotlin.coroutines.Continuation;
import kotlin.reflect.KFunction;
import kotlinx.coroutines.reactive.AwaitKt;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NonNull;
@@ -29,7 +26,6 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -38,7 +34,6 @@ import java.util.stream.Collectors;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.reactivestreams.Publisher;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
@@ -47,8 +42,6 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.core.KotlinDetector;
import org.springframework.core.ResolvableType;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor;
@@ -69,8 +62,6 @@ import org.springframework.data.repository.util.ClassUtils;
import org.springframework.data.repository.util.QueryExecutionConverters;
import org.springframework.data.repository.util.ReactiveWrapperConverters;
import org.springframework.data.repository.util.ReactiveWrappers;
import org.springframework.data.util.KotlinReflectionUtils;
import org.springframework.data.util.Pair;
import org.springframework.data.util.ReflectionUtils;
import org.springframework.lang.Nullable;
import org.springframework.transaction.interceptor.TransactionalProxy;
@@ -335,7 +326,10 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
}
ProjectionFactory projectionFactory = getProjectionFactory(classLoader, beanFactory);
result.addAdvice(new QueryExecutorMethodInterceptor(information, projectionFactory));
Optional<QueryLookupStrategy> queryLookupStrategy = getQueryLookupStrategy(queryLookupStrategyKey,
evaluationContextProvider);
result.addAdvice(new QueryExecutorMethodInterceptor(information, projectionFactory, queryLookupStrategy,
namedQueries, queryPostProcessors));
composition = composition.append(RepositoryFragment.implemented(target));
result.addAdvice(new ImplementationMethodExecutionInterceptor(composition));
@@ -530,124 +524,6 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
baseClass, Arrays.stream(constructorArguments).map(Object::getClass).collect(Collectors.toList()))));
}
/**
* This {@code MethodInterceptor} intercepts calls to methods of the custom implementation and delegates the to it if
* configured. Furthermore it resolves method calls to finders and triggers execution of them. You can rely on having
* a custom repository implementation instance set if this returns true.
*
* @author Oliver Gierke
*/
public class QueryExecutorMethodInterceptor implements MethodInterceptor {
private final Map<Method, RepositoryQuery> queries;
private final Map<Method, QueryMethodInvoker> invocationMetadataCache = new ConcurrentReferenceHashMap<>();
private final QueryExecutionResultHandler resultHandler;
/**
* Creates a new {@link QueryExecutorMethodInterceptor}. Builds a model of {@link QueryMethod}s to be invoked on
* execution of repository interface methods.
*/
public QueryExecutorMethodInterceptor(RepositoryInformation repositoryInformation,
ProjectionFactory projectionFactory) {
this.resultHandler = new QueryExecutionResultHandler(CONVERSION_SERVICE);
Optional<QueryLookupStrategy> lookupStrategy = getQueryLookupStrategy(queryLookupStrategyKey,
RepositoryFactorySupport.this.evaluationContextProvider);
if (!lookupStrategy.isPresent() && repositoryInformation.hasQueryMethods()) {
throw new IllegalStateException("You have defined query method in the repository but "
+ "you don't have any query lookup strategy defined. The "
+ "infrastructure apparently does not support query methods!");
}
this.queries = lookupStrategy //
.map(it -> mapMethodsToQuery(repositoryInformation, it, projectionFactory)) //
.orElse(Collections.emptyMap());
}
private Map<Method, RepositoryQuery> mapMethodsToQuery(RepositoryInformation repositoryInformation,
QueryLookupStrategy lookupStrategy, ProjectionFactory projectionFactory) {
return repositoryInformation.getQueryMethods().stream() //
.map(method -> lookupQuery(method, repositoryInformation, lookupStrategy, projectionFactory)) //
.peek(pair -> invokeListeners(pair.getSecond())) //
.collect(Pair.toMap());
}
private Pair<Method, RepositoryQuery> lookupQuery(Method method, RepositoryInformation information,
QueryLookupStrategy strategy, ProjectionFactory projectionFactory) {
return Pair.of(method, strategy.resolveQuery(method, information, projectionFactory, namedQueries));
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void invokeListeners(RepositoryQuery query) {
for (QueryCreationListener listener : queryPostProcessors) {
ResolvableType typeArgument = ResolvableType.forClass(QueryCreationListener.class, listener.getClass())
.getGeneric(0);
if (typeArgument != null && typeArgument.isAssignableFrom(ResolvableType.forClass(query.getClass()))) {
listener.onCreation(query);
}
}
}
/*
* (non-Javadoc)
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
*/
@Override
@Nullable
public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
QueryExecutionConverters.ExecutionAdapter executionAdapter = QueryExecutionConverters //
.getExecutionAdapter(method.getReturnType());
if (executionAdapter == null) {
return resultHandler.postProcessInvocationResult(doInvoke(invocation), method);
}
return executionAdapter //
.apply(() -> resultHandler.postProcessInvocationResult(doInvoke(invocation), method));
}
@Nullable
private Object doInvoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
if (hasQueryFor(method)) {
QueryMethodInvoker invocationMetadata = invocationMetadataCache.get(method);
if (invocationMetadata == null) {
invocationMetadata = new QueryMethodInvoker(method);
invocationMetadataCache.put(method, invocationMetadata);
}
RepositoryQuery repositoryQuery = queries.get(method);
return invocationMetadata.invoke(repositoryQuery, invocation.getArguments());
}
return invocation.proceed();
}
/**
* Returns whether we know of a query to execute for the given {@link Method};
*
* @param method
* @return
*/
private boolean hasQueryFor(Method method) {
return queries.containsKey(method);
}
}
/**
* Method interceptor that calls methods on the {@link RepositoryComposition}.
*
@@ -727,55 +603,4 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
this.compositionHash = composition.hashCode();
}
}
static class QueryMethodInvoker {
private final boolean suspendedDeclaredMethod;
private final Class<?> returnedType;
private final boolean returnsReactiveType;
QueryMethodInvoker(Method invokedMethod) {
this.suspendedDeclaredMethod = KotlinDetector.isKotlinReflectPresent() && isSuspendedMethod(invokedMethod);
this.returnedType = this.suspendedDeclaredMethod ? KotlinReflectionUtils.getReturnType(invokedMethod)
: invokedMethod.getReturnType();
this.returnsReactiveType = ReactiveWrappers.supports(returnedType);
}
private static boolean isSuspendedMethod(Method invokedMethod) {
KFunction<?> invokedFunction = ReflectionUtils.isKotlinClass(invokedMethod.getDeclaringClass())
? KotlinReflectionUtils.findKotlinFunction(invokedMethod)
: null;
return invokedFunction != null && invokedFunction.isSuspend();
}
@Nullable
public Object invoke(RepositoryQuery query, Object[] args) {
return suspendedDeclaredMethod ? invokeReactiveToSuspend(query, args) : query.execute(args);
}
@Nullable
@SuppressWarnings({ "unchecked", "ConstantConditions" })
private Object invokeReactiveToSuspend(RepositoryQuery query, Object[] args) {
Continuation<Object> continuation = (Continuation) args[args.length - 1];
args[args.length - 1] = null;
Object result = query.execute(args);
if (returnsReactiveType) {
return ReactiveWrapperConverters.toWrapper(result, returnedType);
}
Publisher<?> publisher;
if (result instanceof Publisher) {
publisher = (Publisher<?>) result;
} else {
publisher = ReactiveWrapperConverters.toWrapper(result, Publisher.class);
}
return AwaitKt.awaitFirstOrNull(publisher, continuation);
}
}
}

View File

@@ -28,12 +28,12 @@ import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.util.ClassUtils;
import org.springframework.data.repository.util.QueryExecutionConverters;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Class to abstract a single parameter of a query method. It is held in the context of a {@link Parameters} instance.
@@ -58,13 +58,10 @@ public class Parameter {
List<Class<?>> types = new ArrayList<>(Arrays.asList(Pageable.class, Sort.class));
try {
// consider Kotlin Coroutines Continuation a special parameter. That parameter is synthetic and should not get
// bound to any query.
types.add(ClassUtils.forName("kotlin.coroutines.Continuation", Parameter.class.getClassLoader()));
} catch (ClassNotFoundException e) {
// ignore
}
// consider Kotlin Coroutines Continuation a special parameter. That parameter is synthetic and should not get
// bound to any query.
ClassUtils.ifPresent("kotlin.coroutines.Continuation", Parameter.class.getClassLoader(), types::add);
TYPES = Collections.unmodifiableList(types);
}

View File

@@ -19,6 +19,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.function.Consumer;
import org.springframework.data.repository.Repository;
import org.springframework.data.util.ClassTypeInformation;
@@ -32,6 +33,7 @@ import org.springframework.util.StringUtils;
* Utility class to work with classes.
*
* @author Oliver Gierke
* @author Mark Paluch
*/
public abstract class ClassUtils {
@@ -56,6 +58,30 @@ public abstract class ClassUtils {
return null != ReflectionUtils.findField(type, StringUtils.uncapitalize(property));
}
/**
* Determine whether the {@link Class} identified by the supplied {@code className} is present * and can be loaded and
* call the {@link Consumer action} if the {@link Class} could be loaded.
*
* @param className the name of the class to check.
* @param classLoader the class loader to use.
* @param action the action callback to notify. (may be {@code null} which indicates the default class loader)
* @throws IllegalStateException if the corresponding class is resolvable but there was a readability mismatch in the
* inheritance hierarchy of the class (typically a missing dependency declaration in a Jigsaw module
* definition for a superclass or interface implemented by the class to be checked here)
*/
public static void ifPresent(String className, @Nullable ClassLoader classLoader, Consumer<Class<?>> action) {
try {
Class<?> theClass = org.springframework.util.ClassUtils.forName(className, classLoader);
action.accept(theClass);
} catch (IllegalAccessError err) {
throw new IllegalStateException(
"Readability mismatch in inheritance hierarchy of class [" + className + "]: " + err.getMessage(), err);
} catch (Throwable ex) {
// Typically ClassNotFoundException or NoClassDefFoundError...
}
}
/**
* Returns wthere the given type is the {@link Repository} interface.
*

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2019 the original author or authors.
* Copyright 2019-2020 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.
@@ -26,10 +26,13 @@ import kotlin.reflect.jvm.KTypesJvm;
import kotlin.reflect.jvm.ReflectJvmMapping;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Stream;
import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
/**
@@ -38,11 +41,32 @@ import org.springframework.lang.Nullable;
*
* @author Mark Paluch
* @since 2.3
* @see org.springframework.core.KotlinDetector#isKotlinReflectPresent()
*/
public final class KotlinReflectionUtils {
private static final int KOTLIN_KIND_CLASS = 1;
private KotlinReflectionUtils() {}
/**
* Return {@literal true} if the specified class is a supported Kotlin class. Currently supported are only regular
* Kotlin classes. Other class types (synthetic, SAM, lambdas) are not supported via reflection.
*
* @return {@literal true} if {@code type} is a supported Kotlin class.
*/
public static boolean isSupportedKotlinClass(Class<?> type) {
if (!KotlinDetector.isKotlinType(type)) {
return false;
}
return Arrays.stream(type.getDeclaredAnnotations()) //
.filter(annotation -> annotation.annotationType().getName().equals("kotlin.Metadata")) //
.map(annotation -> AnnotationUtils.getValue(annotation, "k")) //
.anyMatch(it -> Integer.valueOf(KOTLIN_KIND_CLASS).equals(it));
}
/**
* Returns a {@link KFunction} instance corresponding to the given Java {@link Method} instance, or {@code null} if
* this method cannot be represented by a Kotlin function.
@@ -55,14 +79,24 @@ public final class KotlinReflectionUtils {
KFunction<?> kotlinFunction = ReflectJvmMapping.getKotlinFunction(method);
if (kotlinFunction == null) {
// Fallback to own lookup because there's no public Kotlin API for that kind of lookup until
// https://youtrack.jetbrains.com/issue/KT-20768 gets resolved.
return kotlinFunction == null ? findKFunction(method).orElse(null) : kotlinFunction;
}
// Fallback to own lookup because there's no public Kotlin API for that kind of lookup until
// https://youtrack.jetbrains.com/issue/KT-20768 gets resolved.
return findKFunction(method).orElse(null);
}
/**
* Returns whether the {@link Method} is declared as suspend (Kotlin Coroutine).
*
* @param method the method to inspect.
* @return {@literal true} if the method is declared as suspend.
* @see KFunction#isSuspend()
*/
public static boolean isSuspend(Method method) {
return kotlinFunction;
KFunction<?> invokedFunction = KotlinDetector.isKotlinType(method.getDeclaringClass()) ? findKotlinFunction(method)
: null;
return invokedFunction != null && invokedFunction.isSuspend();
}
/**

View File

@@ -31,6 +31,7 @@ import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.springframework.beans.BeanUtils;
import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
@@ -51,10 +52,6 @@ import org.springframework.util.ReflectionUtils.FieldFilter;
@UtilityClass
public class ReflectionUtils {
private static final boolean KOTLIN_IS_PRESENT = ClassUtils.isPresent("kotlin.Unit",
BeanUtils.class.getClassLoader());
private static final int KOTLIN_KIND_CLASS = 1;
/**
* Creates an instance of the class with the given fully qualified name or returns the given default instance if the
* class cannot be loaded or instantiated.
@@ -350,12 +347,11 @@ public class ReflectionUtils {
*
* @return {@literal true} if {@code type} is a Kotlin class.
* @since 2.0
* @deprecated since 2.3, use {@link KotlinDetector#isKotlinType(Class)} instead.
*/
@Deprecated
public static boolean isKotlinClass(Class<?> type) {
return KOTLIN_IS_PRESENT && Arrays.stream(type.getDeclaredAnnotations()) //
.map(Annotation::annotationType) //
.anyMatch(annotation -> annotation.getName().equals("kotlin.Metadata"));
return KotlinDetector.isKotlinType(type);
}
/**
@@ -364,17 +360,11 @@ public class ReflectionUtils {
*
* @return {@literal true} if {@code type} is a supported Kotlin class.
* @since 2.0
* @deprecated since 2.3, use {@link KotlinReflectionUtils#isSupportedKotlinClass(Class)} instead.
*/
@Deprecated
public static boolean isSupportedKotlinClass(Class<?> type) {
if (!isKotlinClass(type)) {
return false;
}
return Arrays.stream(type.getDeclaredAnnotations()) //
.filter(annotation -> annotation.annotationType().getName().equals("kotlin.Metadata")) //
.map(annotation -> AnnotationUtils.getValue(annotation, "k")) //
.anyMatch(it -> Integer.valueOf(KOTLIN_KIND_CLASS).equals(it));
return KotlinReflectionUtils.isSupportedKotlinClass(type);
}
/**