From 771347bf7d4b6808705f6113f06c712a0c2059fe Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 6 Nov 2024 15:57:13 +0100 Subject: [PATCH] Revise `ClassUtils` and `ReflectionUtils`. We deprecated `ClassUtils` in the repo.utils package and introduced a slimmer variant in o.s.d.util. Also, ReflectionUtils hosts now several methods that have been in ClassUtils along with an improved method naming (find vs. get in combination with return value semantics). CastUtils is deprecated as we do not widely use it. --- .../model/AbstractPersistentProperty.java | 2 +- .../binding/QuerydslPredicateBuilder.java | 2 +- .../config/RepositoryComponentProvider.java | 19 ++- .../RepositoryConfigurationDelegate.java | 15 +- .../core/RepositoryInformationSupport.java | 30 +++- .../support/QueryExecutionResultHandler.java | 4 +- .../support/RepositoryFactorySupport.java | 32 ++-- .../data/repository/query/Parameter.java | 2 +- .../data/repository/query/QueryMethod.java | 18 ++- .../data/repository/util/ClassUtils.java | 28 +--- ...EvaluationContextExtensionInformation.java | 2 +- ...tensionAwareEvaluationContextProvider.java | 2 +- .../springframework/data/util/BeanLookup.java | 2 +- .../springframework/data/util/CastUtils.java | 4 + .../springframework/data/util/ClassUtils.java | 78 +++++++++ .../data/util/ReflectionUtils.java | 148 +++++++++++++++--- .../CoroutineRepositoryMetadataUnitTests.kt | 27 +++- 17 files changed, 321 insertions(+), 94 deletions(-) create mode 100644 src/main/java/org/springframework/data/util/ClassUtils.java diff --git a/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java b/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java index c8af5bd5a..616a73475 100644 --- a/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java +++ b/src/main/java/org/springframework/data/mapping/model/AbstractPersistentProperty.java @@ -49,7 +49,7 @@ public abstract class AbstractPersistentProperty

static { - CAUSE_FIELD = ReflectionUtils.findRequiredField(Throwable.class, "cause"); + CAUSE_FIELD = ReflectionUtils.getRequiredField(Throwable.class, "cause"); ASSOCIATION_TYPE = ReflectionUtils.loadIfPresent("org.jmolecules.ddd.types.Association", AbstractPersistentProperty.class.getClassLoader()); } diff --git a/src/main/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilder.java b/src/main/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilder.java index 5e6d1868a..97dcca56a 100644 --- a/src/main/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilder.java +++ b/src/main/java/org/springframework/data/querydsl/binding/QuerydslPredicateBuilder.java @@ -219,7 +219,7 @@ public class QuerydslPredicateBuilder { TypeDescriptor result = descriptor == null // ? TypeDescriptor - .nested(org.springframework.data.util.ReflectionUtils.findRequiredField(owningType, leafProperty), 0) + .nested(org.springframework.data.util.ReflectionUtils.getRequiredField(owningType, leafProperty), 0) : TypeDescriptor .nested(new Property(owningType, descriptor.getReadMethod(), descriptor.getWriteMethod(), leafProperty), 0); diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryComponentProvider.java b/src/main/java/org/springframework/data/repository/config/RepositoryComponentProvider.java index 7999b9826..776b57ab3 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryComponentProvider.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryComponentProvider.java @@ -33,8 +33,8 @@ import org.springframework.core.type.filter.TypeFilter; import org.springframework.data.repository.NoRepositoryBean; import org.springframework.data.repository.Repository; import org.springframework.data.repository.RepositoryDefinition; -import org.springframework.data.repository.util.ClassUtils; import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -103,7 +103,7 @@ class RepositoryComponentProvider extends ClassPathScanningCandidateComponentPro @Override protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { - boolean isNonRepositoryInterface = !ClassUtils.isGenericRepositoryInterface(beanDefinition.getBeanClassName()); + boolean isNonRepositoryInterface = !isGenericRepositoryInterface(beanDefinition.getBeanClassName()); boolean isTopLevelType = !beanDefinition.getMetadata().hasEnclosingClass(); boolean isConsiderNestedRepositories = isConsiderNestedRepositoryInterfaces(); @@ -150,6 +150,13 @@ class RepositoryComponentProvider extends ClassPathScanningCandidateComponentPro this.considerNestedRepositoryInterfaces = considerNestedRepositoryInterfaces; } + /** + * Returns whether the given type name is a {@link Repository} interface name. + */ + private static boolean isGenericRepositoryInterface(@Nullable String interfaceName) { + return Repository.class.getName().equals(interfaceName); + } + /** * {@link org.springframework.core.type.filter.TypeFilter} that only matches interfaces. Thus setting this up makes * only sense providing an interface type as {@code targetType}. @@ -180,21 +187,19 @@ class RepositoryComponentProvider extends ClassPathScanningCandidateComponentPro * * @author Oliver Gierke */ - private static class AllTypeFilter implements TypeFilter { - - private final List delegates; + private record AllTypeFilter(List delegates) implements TypeFilter { /** * Creates a new {@link AllTypeFilter} to match if all the given delegates match. * * @param delegates must not be {@literal null}. */ - public AllTypeFilter(List delegates) { + private AllTypeFilter { Assert.notNull(delegates, "TypeFilter deleages must not be null"); - this.delegates = delegates; } + @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java index 21e456913..136b9a12a 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java @@ -26,6 +26,7 @@ import java.util.stream.Collectors; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.DependencyDescriptor; @@ -51,10 +52,9 @@ import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.AbstractRepositoryMetadata; import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; import org.springframework.data.repository.core.support.RepositoryFactorySupport; -import org.springframework.data.util.ReflectionUtils; +import org.springframework.data.util.ClassUtils; import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; import org.springframework.util.StopWatch; /** @@ -123,8 +123,7 @@ public class RepositoryConfigurationDelegate { return environment; } - return resourceLoader instanceof EnvironmentCapable capable ? capable.getEnvironment() - : new StandardEnvironment(); + return resourceLoader instanceof EnvironmentCapable capable ? capable.getEnvironment() : new StandardEnvironment(); } /** @@ -321,19 +320,19 @@ public class RepositoryConfigurationDelegate { private ResolvableType getRepositoryFactoryBeanType(RepositoryConfiguration configuration) { String interfaceName = configuration.getRepositoryInterface(); - ClassLoader classLoader = resourceLoader.getClassLoader() == null ? ClassUtils.getDefaultClassLoader() + ClassLoader classLoader = resourceLoader.getClassLoader() == null + ? org.springframework.util.ClassUtils.getDefaultClassLoader() : resourceLoader.getClassLoader(); classLoader = classLoader != null ? classLoader : getClass().getClassLoader(); - Class repositoryInterface = ReflectionUtils.loadIfPresent(interfaceName, classLoader); + Class repositoryInterface = ClassUtils.loadIfPresent(interfaceName, classLoader); if (repositoryInterface == null) { return null; } - Class factoryBean = ReflectionUtils.loadIfPresent(configuration.getRepositoryFactoryBeanClassName(), - classLoader); + Class factoryBean = ClassUtils.loadIfPresent(configuration.getRepositoryFactoryBeanClassName(), classLoader); if (factoryBean == null) { return null; diff --git a/src/main/java/org/springframework/data/repository/core/RepositoryInformationSupport.java b/src/main/java/org/springframework/data/repository/core/RepositoryInformationSupport.java index ddc85065f..a2a2341f2 100644 --- a/src/main/java/org/springframework/data/repository/core/RepositoryInformationSupport.java +++ b/src/main/java/org/springframework/data/repository/core/RepositoryInformationSupport.java @@ -15,8 +15,6 @@ */ package org.springframework.data.repository.core; -import static org.springframework.data.repository.util.ClassUtils.*; - import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; @@ -25,9 +23,12 @@ import java.util.function.Supplier; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.annotation.QueryAnnotation; +import org.springframework.data.repository.Repository; import org.springframework.data.util.Lazy; import org.springframework.data.util.Streamable; import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Contract; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -166,8 +167,8 @@ public abstract class RepositoryInformationSupport implements RepositoryInformat Class repositoryInterface = getRepositoryInterface(); return new DefaultQueryMethods(Streamable.of(Arrays.stream(repositoryInterface.getMethods()) - .map(it -> ClassUtils.getMostSpecificMethod(it, repositoryInterface)) - .filter(this::isQueryMethodCandidate) + .map(it -> ClassUtils.getMostSpecificMethod(it, repositoryInterface)) // + .filter(this::isQueryMethodCandidate) // .toList()), calculateHasCustomMethod(repositoryInterface)); } @@ -187,6 +188,27 @@ public abstract class RepositoryInformationSupport implements RepositoryInformat return false; } + /** + * Returns where the given type is the {@link Repository} interface. + * + * @param ifc + * @return + */ + private static boolean isGenericRepositoryInterface(Class ifc) { + return Repository.class.equals(ifc); + } + + /** + * Returns whether the given type name is a repository interface name. + * + * @param interfaceName + * @return + */ + @Contract("null -> false") + public static boolean isGenericRepositoryInterface(@Nullable String interfaceName) { + return Repository.class.getName().equals(interfaceName); + } + /** * Information about query methods to allow canonical computation and reuse of that information. * diff --git a/src/main/java/org/springframework/data/repository/core/support/QueryExecutionResultHandler.java b/src/main/java/org/springframework/data/repository/core/support/QueryExecutionResultHandler.java index 9438a3fc9..966455cf4 100644 --- a/src/main/java/org/springframework/data/repository/core/support/QueryExecutionResultHandler.java +++ b/src/main/java/org/springframework/data/repository/core/support/QueryExecutionResultHandler.java @@ -27,7 +27,6 @@ import org.springframework.core.MethodParameter; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.support.GenericConversionService; -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.util.NullableWrapper; @@ -67,7 +66,8 @@ class QueryExecutionResultHandler { public static Class loadIfPresent(String type) { try { - return (Class) org.springframework.util.ClassUtils.forName(type, ClassUtils.class.getClassLoader()); + return (Class) org.springframework.util.ClassUtils.forName(type, + QueryExecutionResultHandler.class.getClassLoader()); } catch (ClassNotFoundException | LinkageError e) { return null; } diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java index b2b1a728f..af1c6ad71 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java @@ -17,6 +17,7 @@ package org.springframework.data.repository.core.support; import java.io.Serializable; import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; @@ -184,7 +185,7 @@ public abstract class RepositoryFactorySupport @Override public void setBeanClassLoader(@Nullable ClassLoader classLoader) { - this.classLoader = classLoader == null ? org.springframework.util.ClassUtils.getDefaultClassLoader() : classLoader; + this.classLoader = classLoader == null ? ClassUtils.getDefaultClassLoader() : classLoader; this.projectionFactory = createProjectionFactory(); } @@ -429,15 +430,15 @@ public abstract class RepositoryFactorySupport if (DefaultMethodInvokingMethodInterceptor.hasDefaultMethods(repositoryInterface)) { if (logger.isTraceEnabled()) { - logger.trace(LogMessage.format("Register DefaultMethodInvokingMethodInterceptor for %s…", repositoryInterface.getName())); + logger.trace(LogMessage.format("Register DefaultMethodInvokingMethodInterceptor for %s…", + repositoryInterface.getName())); } result.addAdvice(new DefaultMethodInvokingMethodInterceptor()); } Optional queryLookupStrategy = getQueryLookupStrategy(queryLookupStrategyKey, new ValueExpressionDelegate( - new QueryMethodValueEvaluationContextAccessor(getEnvironment(), evaluationContextProvider), - VALUE_PARSER)); + new QueryMethodValueEvaluationContextAccessor(getEnvironment(), evaluationContextProvider), VALUE_PARSER)); result.addAdvice(new QueryExecutorMethodInterceptor(information, getProjectionFactory(), queryLookupStrategy, namedQueries, queryPostProcessors, methodInvocationListeners)); @@ -529,7 +530,7 @@ public abstract class RepositoryFactorySupport return repositoryInformationCache.computeIfAbsent(cacheKey, key -> { - Class baseClass = repositoryBaseClass != null ? repositoryBaseClass : getRepositoryBaseClass(metadata); + Class baseClass = repositoryBaseClass != null ? repositoryBaseClass : getRepositoryBaseClass(metadata); return new DefaultRepositoryInformation(metadata, baseClass, composition); }); @@ -751,11 +752,13 @@ public abstract class RepositoryFactorySupport try { return composition.invoke(invocationMulticaster, method, arguments); - } catch (Exception e) { - org.springframework.data.repository.util.ClassUtils.unwrapReflectionException(e); - } + } catch (Exception ex) { + if (ex instanceof InvocationTargetException) { + throw ((InvocationTargetException) ex).getTargetException(); + } - throw new IllegalStateException("Should not occur"); + throw ex; + } } } @@ -886,25 +889,24 @@ public abstract class RepositoryFactorySupport static { - org.springframework.data.repository.util.ClassUtils.ifPresent( - "org.springframework.data.querydsl.QuerydslPredicateExecutor", RepositoryValidator.class.getClassLoader(), - it -> { + org.springframework.data.util.ClassUtils.ifPresent("org.springframework.data.querydsl.QuerydslPredicateExecutor", + RepositoryValidator.class.getClassLoader(), it -> { WELL_KNOWN_EXECUTORS.put(it, "Querydsl"); }); - org.springframework.data.repository.util.ClassUtils.ifPresent( + org.springframework.data.util.ClassUtils.ifPresent( "org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor", RepositoryValidator.class.getClassLoader(), it -> { WELL_KNOWN_EXECUTORS.put(it, "Reactive Querydsl"); }); - org.springframework.data.repository.util.ClassUtils.ifPresent( + org.springframework.data.util.ClassUtils.ifPresent( "org.springframework.data.repository.query.QueryByExampleExecutor", RepositoryValidator.class.getClassLoader(), it -> { WELL_KNOWN_EXECUTORS.put(it, "Query by Example"); }); - org.springframework.data.repository.util.ClassUtils.ifPresent( + org.springframework.data.util.ClassUtils.ifPresent( "org.springframework.data.repository.query.ReactiveQueryByExampleExecutor", RepositoryValidator.class.getClassLoader(), it -> { WELL_KNOWN_EXECUTORS.put(it, "Reactive Query by Example"); diff --git a/src/main/java/org/springframework/data/repository/query/Parameter.java b/src/main/java/org/springframework/data/repository/query/Parameter.java index 89205e16e..97eccfbd5 100644 --- a/src/main/java/org/springframework/data/repository/query/Parameter.java +++ b/src/main/java/org/springframework/data/repository/query/Parameter.java @@ -30,9 +30,9 @@ import org.springframework.data.domain.Limit; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.ScrollPosition; import org.springframework.data.domain.Sort; -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.util.ClassUtils; import org.springframework.data.util.Lazy; import org.springframework.data.util.TypeInformation; import org.springframework.util.Assert; diff --git a/src/main/java/org/springframework/data/repository/query/QueryMethod.java b/src/main/java/org/springframework/data/repository/query/QueryMethod.java index 5528c3e35..5a3f88769 100644 --- a/src/main/java/org/springframework/data/repository/query/QueryMethod.java +++ b/src/main/java/org/springframework/data/repository/query/QueryMethod.java @@ -15,8 +15,6 @@ */ package org.springframework.data.repository.query; -import static org.springframework.data.repository.util.ClassUtils.*; - import java.lang.reflect.Method; import java.util.Collections; import java.util.Set; @@ -38,6 +36,7 @@ import org.springframework.data.repository.util.ReactiveWrapperConverters; import org.springframework.data.util.Lazy; import org.springframework.data.util.NullableWrapperConverters; import org.springframework.data.util.ReactiveWrappers; +import org.springframework.data.util.ReflectionUtils; import org.springframework.data.util.TypeInformation; import org.springframework.util.Assert; @@ -77,7 +76,9 @@ public class QueryMethod { Assert.notNull(factory, "ProjectionFactory must not be null"); Parameters.TYPES.stream() // - .filter(type -> getNumberOfOccurrences(method, type) > 1).findFirst().ifPresent(type -> { + .filter(type -> ReflectionUtils.getParameterCount(method, type::equals) > 1) // + .findFirst() // + .ifPresent(type -> { throw new IllegalStateException(String.format( "Method must have only one argument of type %s; Offending method: %s", type.getSimpleName(), method)); }); @@ -107,19 +108,19 @@ public class QueryMethod { QueryMethodValidator.validate(method); - if (hasParameterOfType(method, Pageable.class)) { + if (ReflectionUtils.hasParameterOfType(method, Pageable.class)) { if (!isStreamQuery()) { assertReturnTypeAssignable(method, QueryExecutionConverters.getAllowedPageableTypes()); } - if (hasParameterOfType(method, Sort.class)) { + if (ReflectionUtils.hasParameterOfType(method, Sort.class)) { throw new IllegalStateException(String.format("Method must not have Pageable *and* Sort parameters. " + "Use sorting capabilities on Pageable instead; Offending method: %s", method)); } } - if (hasParameterOfType(method, ScrollPosition.class)) { + if (ReflectionUtils.hasParameterOfType(method, ScrollPosition.class)) { assertReturnTypeAssignable(method, Collections.singleton(Window.class)); } @@ -388,11 +389,12 @@ public class QueryMethod { static Predicate pageableCannotHaveSortOrLimit = (method) -> { - if (!hasParameterAssignableToType(method, Pageable.class)) { + if (!ReflectionUtils.hasParameterAssignableToType(method, Pageable.class)) { return true; } - if (hasParameterAssignableToType(method, Sort.class) || hasParameterAssignableToType(method, Limit.class)) { + if (ReflectionUtils.hasParameterAssignableToType(method, Sort.class) + || ReflectionUtils.hasParameterAssignableToType(method, Limit.class)) { return false; } diff --git a/src/main/java/org/springframework/data/repository/util/ClassUtils.java b/src/main/java/org/springframework/data/repository/util/ClassUtils.java index 37fd9cad9..a57502ed2 100644 --- a/src/main/java/org/springframework/data/repository/util/ClassUtils.java +++ b/src/main/java/org/springframework/data/repository/util/ClassUtils.java @@ -19,7 +19,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collection; -import java.util.List; import java.util.function.Consumer; import org.springframework.data.repository.Repository; @@ -36,7 +35,9 @@ import org.springframework.util.StringUtils; * @author Oliver Gierke * @author Mark Paluch * @author Johannes Englmeier + * @deprecated since 3.5, use {@link org.springframework.data.util.ClassUtils} instead. */ +@Deprecated(since = "3.5", forRemoval = true) public abstract class ClassUtils { /** @@ -72,16 +73,7 @@ public abstract class ClassUtils { * definition for a superclass or interface implemented by the class to be checked here) */ public static void ifPresent(String className, @Nullable ClassLoader classLoader, Consumer> 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... - } + org.springframework.data.util.ClassUtils.ifPresent(className, classLoader, action); } /** @@ -108,7 +100,6 @@ public abstract class ClassUtils { /** * @deprecated Use {@link #getNumberOfOccurrences(Method, Class)}. */ - @Deprecated public static int getNumberOfOccurences(Method method, Class type) { return getNumberOfOccurrences(method, type); } @@ -124,10 +115,7 @@ public abstract class ClassUtils { * @see java.lang.reflect.Method#getParameterTypes() */ public static int getNumberOfOccurrences(@NonNull Method method, @NonNull Class parameterType) { - - return (int) Arrays.stream(method.getParameterTypes()) - .filter(parameterType::equals) - .count(); + return org.springframework.data.util.ReflectionUtils.getParameterCount(method, parameterType::equals); } /** @@ -174,7 +162,7 @@ public abstract class ClassUtils { * @return */ public static boolean hasParameterOfType(Method method, Class type) { - return Arrays.asList(method.getParameterTypes()).contains(type); + return org.springframework.data.util.ReflectionUtils.hasParameterOfType(method, type); } /** @@ -185,7 +173,7 @@ public abstract class ClassUtils { * @return */ public static boolean hasParameterAssignableToType(Method method, Class type) { - return List.of(method.getParameterTypes()).stream().anyMatch(type::isAssignableFrom); + return org.springframework.data.util.ReflectionUtils.hasParameterOfType(method, type); } /** @@ -196,8 +184,8 @@ public abstract class ClassUtils { */ public static void unwrapReflectionException(Exception ex) throws Throwable { - if (ex instanceof InvocationTargetException) { - throw ((InvocationTargetException) ex).getTargetException(); + if (ex instanceof InvocationTargetException ite) { + ReflectionUtils.handleInvocationTargetException(ite); } throw ex; diff --git a/src/main/java/org/springframework/data/spel/EvaluationContextExtensionInformation.java b/src/main/java/org/springframework/data/spel/EvaluationContextExtensionInformation.java index c7895ebbd..a4f79ddf2 100644 --- a/src/main/java/org/springframework/data/spel/EvaluationContextExtensionInformation.java +++ b/src/main/java/org/springframework/data/spel/EvaluationContextExtensionInformation.java @@ -74,7 +74,7 @@ class EvaluationContextExtensionInformation { Assert.notNull(type, "Extension type must not be null"); - Class rootObjectType = org.springframework.data.util.ReflectionUtils.findRequiredMethod(type, "getRootObject") + Class rootObjectType = org.springframework.data.util.ReflectionUtils.getRequiredMethod(type, "getRootObject") .getReturnType(); this.rootObjectInformation = Optional diff --git a/src/main/java/org/springframework/data/spel/ReactiveExtensionAwareEvaluationContextProvider.java b/src/main/java/org/springframework/data/spel/ReactiveExtensionAwareEvaluationContextProvider.java index bdc68bfb5..65352f6e4 100644 --- a/src/main/java/org/springframework/data/spel/ReactiveExtensionAwareEvaluationContextProvider.java +++ b/src/main/java/org/springframework/data/spel/ReactiveExtensionAwareEvaluationContextProvider.java @@ -139,7 +139,7 @@ public class ReactiveExtensionAwareEvaluationContextProvider implements Reactive private static ResolvableType getExtensionType(ExtensionIdAware extensionCandidate) { return ResolvableType - .forMethodReturnType(ReflectionUtils.findRequiredMethod(extensionCandidate.getClass(), "getExtension")) + .forMethodReturnType(ReflectionUtils.getRequiredMethod(extensionCandidate.getClass(), "getExtension")) .getGeneric(0); } } diff --git a/src/main/java/org/springframework/data/util/BeanLookup.java b/src/main/java/org/springframework/data/util/BeanLookup.java index 3c2470d28..cf602a6ba 100644 --- a/src/main/java/org/springframework/data/util/BeanLookup.java +++ b/src/main/java/org/springframework/data/util/BeanLookup.java @@ -41,7 +41,7 @@ public abstract class BeanLookup { * beans of the given type are available in the given {@link BeanFactory}. * * @param type must not be {@literal null}. - * @param beanFactory the {@link BeanFactory} to lookup the bean from. + * @param beanFactory the {@link BeanFactory} to look up the bean from. * @return a {@link Lazy} for the unique bean of the given type or the instance provided by the fallback in case no * bean of the given type can be found. */ diff --git a/src/main/java/org/springframework/data/util/CastUtils.java b/src/main/java/org/springframework/data/util/CastUtils.java index c9df96626..902eb8a36 100644 --- a/src/main/java/org/springframework/data/util/CastUtils.java +++ b/src/main/java/org/springframework/data/util/CastUtils.java @@ -15,6 +15,10 @@ */ package org.springframework.data.util; +/** + * @deprecated since 3.5 will be removed in a future release. + */ +@Deprecated(since = "3.5", forRemoval = true) public interface CastUtils { @SuppressWarnings("unchecked") diff --git a/src/main/java/org/springframework/data/util/ClassUtils.java b/src/main/java/org/springframework/data/util/ClassUtils.java new file mode 100644 index 000000000..eecaf4ae2 --- /dev/null +++ b/src/main/java/org/springframework/data/util/ClassUtils.java @@ -0,0 +1,78 @@ +/* + * Copyright 2008-2024 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.util; + +import java.util.function.Consumer; + +import org.springframework.lang.Nullable; + +/** + * Utility class to work with classes. + * + * @author Oliver Gierke + * @author Mark Paluch + * @author Johannes Englmeier + * @since 3.5 + */ +public abstract class ClassUtils { + + /** + * Private constructor to prevent instantiation. + */ + private ClassUtils() {} + + /** + * 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 (can be {@literal null}, which indicates the default class loader). + * @param action the action callback to notify. + * @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> 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... + } + } + + /** + * Loads the class with the given name using the given {@link ClassLoader}. + * + * @param name the name of the class to be loaded. + * @param classLoader the class loader to use (can be {@literal null}, which indicates the default class loader). + * @return the {@link Class} or {@literal null} in case the class can't be loaded for any reason. + */ + @Nullable + public static Class loadIfPresent(String name, @Nullable ClassLoader classLoader) { + + try { + return org.springframework.util.ClassUtils.forName(name, classLoader); + } catch (Exception o_O) { + return null; + } + } + +} diff --git a/src/main/java/org/springframework/data/util/ReflectionUtils.java b/src/main/java/org/springframework/data/util/ReflectionUtils.java index 2809f2ec3..fbee7fdaf 100644 --- a/src/main/java/org/springframework/data/util/ReflectionUtils.java +++ b/src/main/java/org/springframework/data/util/ReflectionUtils.java @@ -19,11 +19,13 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.IntStream; import java.util.stream.Stream; import org.springframework.beans.BeanUtils; @@ -52,6 +54,50 @@ public final class ReflectionUtils { throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); } + /** + * Returns whether the given {@link Method} has a parameter of the given type. + * + * @param method the method to check, must not be {@literal null}. + * @param type parameter type to query for, must not be {@literal null}. + * @return {@literal true} the given {@link Method} has a parameter of the given type. + * @since 3.5 + */ + public static boolean hasParameterOfType(Method method, Class type) { + return Arrays.asList(method.getParameterTypes()).contains(type); + } + + /** + * Returns whether the given {@link Method} has a parameter that is assignable to the given type. + * + * @param method the method to check, must not be {@literal null}. + * @param type parameter type to query for, must not be {@literal null}. + * @return {@literal true} the given {@link Method} has a parameter that is assignable to the given type. + * @since 3.5 + */ + public static boolean hasParameterAssignableToType(Method method, Class type) { + + for (Class parameterType : method.getParameterTypes()) { + if (type.isAssignableFrom(parameterType)) { + return true; + } + } + + return false; + } + + /** + * Returns the number of matching parameters {@link Method} for {@link Predicate}. + * + * @param method {@link Method} to evaluate. + * @param predicate the predicate matching {@link Method} + * @return the resulting number of matching parameters. + * @see java.lang.reflect.Method#getParameterTypes() + * @since 3.5 + */ + public static int getParameterCount(Method method, Predicate> predicate) { + return (int) Arrays.stream(method.getParameterTypes()).filter(predicate).count(); + } + /** * 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. @@ -59,8 +105,10 @@ public final class ReflectionUtils { * @param classname the fully qualified class name to create an instance for. * @param defaultInstance the instance to fall back to in case the given class cannot be loaded or instantiated. * @return + * @deprecated since 3.5 as it is not used within the framework anymore. */ @SuppressWarnings("unchecked") + @Deprecated(since = "3.5", forRemoval = true) public static T createInstanceIfPresent(String classname, T defaultInstance) { try { @@ -106,6 +154,7 @@ public final class ReflectionUtils { * @return */ String getDescription(); + } /** @@ -129,6 +178,7 @@ public final class ReflectionUtils { public String getDescription() { return String.format("Annotation filter for %s", annotationType.getName()); } + } /** @@ -151,6 +201,7 @@ public final class ReflectionUtils { public String getDescription() { return String.format("FieldFilter %s", filter.toString()); } + }, false); } @@ -200,7 +251,7 @@ public final class ReflectionUtils { return field; } - if (foundField != null && enforceUniqueness) { + if (foundField != null) { throw new IllegalStateException(filter.getDescription()); } @@ -218,10 +269,25 @@ public final class ReflectionUtils { * * @param type must not be {@literal null}. * @param name must not be {@literal null} or empty. - * @return + * @return the required field. + * @throws IllegalArgumentException in case the field can't be found. + * @deprecated use {@link #getRequiredField(Class, String)} instead. + */ + @Deprecated(since = "3.5", forRemoval = true) + public static Field findRequiredField(Class type, String name) { + return getRequiredField(type, name); + } + + /** + * Obtains the required field of the given name on the given type or throws {@link IllegalArgumentException} if the + * found could not be found. + * + * @param type must not be {@literal null}. + * @param name must not be {@literal null} or empty. + * @return the required field. * @throws IllegalArgumentException in case the field can't be found. */ - public static Field findRequiredField(Class type, String name) { + public static Field getRequiredField(Class type, String name) { Field result = org.springframework.util.ReflectionUtils.findField(type, name); @@ -251,7 +317,9 @@ public final class ReflectionUtils { * @param type must not be {@literal null}. * @param constructorArguments must not be {@literal null}. * @return a {@link Constructor} that is compatible with the given arguments. + * @deprecated since 3.5, return type will change to nullable instead of Optional. */ + @Deprecated public static Optional> findConstructor(Class type, Object... constructorArguments) { Assert.notNull(type, "Target type must not be null"); @@ -271,8 +339,25 @@ public final class ReflectionUtils { * @param parameterTypes must not be {@literal null}. * @return the method object. * @throws IllegalArgumentException in case the method cannot be resolved. + * @deprecated since 3.5, use {@link #getRequiredMethod(Class, String, Class[])} instead. */ + @Deprecated public static Method findRequiredMethod(Class type, String name, Class... parameterTypes) { + return getRequiredMethod(type, name, parameterTypes); + } + + /** + * Returns the method with the given name of the given class and parameter types. Prefers regular methods over + * {@link Method#isBridge() bridge} and {@link Method#isSynthetic() synthetic} ones. + * + * @param type must not be {@literal null}. + * @param name must not be {@literal null}. + * @param parameterTypes must not be {@literal null}. + * @return the method object. + * @throws IllegalArgumentException in case the method cannot be resolved. + * @since 3.5 + */ + public static Method getRequiredMethod(Class type, String name, Class... parameterTypes) { Assert.notNull(type, "Class must not be null"); Assert.notNull(name, "Method name must not be null"); @@ -313,7 +398,7 @@ public final class ReflectionUtils { * Returns a {@link Stream} of the return and parameters types of the given {@link Method}. * * @param method must not be {@literal null}. - * @return + * @return stream of return and parameter types. * @since 2.0 */ public static Stream> returnTypeAndParameters(Method method) { @@ -332,25 +417,51 @@ public final class ReflectionUtils { * @param type must not be {@literal null}. * @param name must not be {@literal null} or empty. * @param parameterTypes must not be {@literal null}. - * @return + * @return the optional Method. * @since 2.0 */ + @Deprecated(since = "3.5", forRemoval = true) public static Optional getMethod(Class type, String name, ResolvableType... parameterTypes) { + return Optional.ofNullable(findMethod(type, name, parameterTypes)); + } + + /** + * Returns the {@link Method} with the given name and parameters declared on the given type, if available. + * + * @param type must not be {@literal null}. + * @param name must not be {@literal null} or empty. + * @param parameterTypes must not be {@literal null}. + * @return the required method. + * @since 3.5 + */ + @Nullable + public static Method findMethod(Class type, String name, ResolvableType... parameterTypes) { Assert.notNull(type, "Type must not be null"); Assert.hasText(name, "Name must not be null or empty"); Assert.notNull(parameterTypes, "Parameter types must not be null"); - List> collect = Arrays.stream(parameterTypes)// - .map(ResolvableType::getRawClass)// - .collect(Collectors.toList()); + List> collect = parameterTypes.length == 0 ? Collections.emptyList() + : new ArrayList<>(parameterTypes.length); + for (ResolvableType parameterType : parameterTypes) { + Class rawClass = parameterType.getRawClass(); + collect.add(rawClass); + } - Method method = org.springframework.util.ReflectionUtils.findMethod(type, name, - collect.toArray(new Class[collect.size()])); + Method method = org.springframework.util.ReflectionUtils.findMethod(type, name, collect.toArray(new Class[0])); - return Optional.ofNullable(method)// - .filter(it -> IntStream.range(0, it.getParameterCount())// - .allMatch(index -> ResolvableType.forMethodParameter(it, index).equals(parameterTypes[index]))); + if (method != null) { + + for (int i = 0; i < parameterTypes.length; i++) { + if (!ResolvableType.forMethodParameter(method, i).equals(parameterTypes[i])) { + return null; + } + } + + return method; + } + + return null; } private static boolean argumentsMatch(Class[] parameterTypes, Object[] arguments) { @@ -455,12 +566,9 @@ public final class ReflectionUtils { * @since 2.5 */ @Nullable + @Deprecated(since = "3.5", forRemoval = true) public static Class loadIfPresent(String name, ClassLoader classLoader) { - - try { - return ClassUtils.forName(name, classLoader); - } catch (Exception o_O) { - return null; - } + return org.springframework.data.util.ClassUtils.loadIfPresent(name, classLoader); } + } diff --git a/src/test/kotlin/org/springframework/data/repository/kotlin/CoroutineRepositoryMetadataUnitTests.kt b/src/test/kotlin/org/springframework/data/repository/kotlin/CoroutineRepositoryMetadataUnitTests.kt index 729ebb6a0..245249480 100644 --- a/src/test/kotlin/org/springframework/data/repository/kotlin/CoroutineRepositoryMetadataUnitTests.kt +++ b/src/test/kotlin/org/springframework/data/repository/kotlin/CoroutineRepositoryMetadataUnitTests.kt @@ -34,7 +34,12 @@ class CoroutineRepositoryMetadataUnitTests { fun shouldDetermineCorrectResultType() { val metadata = DefaultRepositoryMetadata(MyCoRepository::class.java) - val method = ReflectionUtils.findRequiredMethod(MyCoRepository::class.java, "findOne", String::class.java, Continuation::class.java); + val method = ReflectionUtils.getRequiredMethod( + MyCoRepository::class.java, + "findOne", + String::class.java, + Continuation::class.java + ); assertThat(metadata.getReturnedDomainClass(method)).isEqualTo(User::class.java) } @@ -43,7 +48,12 @@ class CoroutineRepositoryMetadataUnitTests { fun shouldDetermineCorrectOptionalResultType() { val metadata = DefaultRepositoryMetadata(MyCoRepository::class.java) - val method = ReflectionUtils.findRequiredMethod(MyCoRepository::class.java, "findOneOptional", String::class.java, Continuation::class.java); + val method = ReflectionUtils.getRequiredMethod( + MyCoRepository::class.java, + "findOneOptional", + String::class.java, + Continuation::class.java + ); assertThat(metadata.getReturnedDomainClass(method)).isEqualTo(User::class.java) } @@ -52,7 +62,11 @@ class CoroutineRepositoryMetadataUnitTests { fun shouldDetermineCorrectFlowResultType() { val metadata = DefaultRepositoryMetadata(MyCoRepository::class.java) - val method = ReflectionUtils.findRequiredMethod(MyCoRepository::class.java, "findMultiple", String::class.java); + val method = ReflectionUtils.getRequiredMethod( + MyCoRepository::class.java, + "findMultiple", + String::class.java + ); assertThat(metadata.getReturnedDomainClass(method)).isEqualTo(User::class.java) } @@ -61,7 +75,12 @@ class CoroutineRepositoryMetadataUnitTests { fun shouldDetermineCorrectSuspendedFlowResultType() { val metadata = DefaultRepositoryMetadata(MyCoRepository::class.java) - val method = ReflectionUtils.findRequiredMethod(MyCoRepository::class.java, "findMultipleSuspended", String::class.java, Continuation::class.java); + val method = ReflectionUtils.getRequiredMethod( + MyCoRepository::class.java, + "findMultipleSuspended", + String::class.java, + Continuation::class.java + ); assertThat(metadata.getReturnedDomainClass(method)).isEqualTo(User::class.java) }