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 | 146 +++++++++++++++--- .../CoroutineRepositoryMetadataUnitTests.kt | 27 +++- 17 files changed, 320 insertions(+), 93 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 c8af5bd5a3..616a734750 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 5e6d1868a7..97dcca56a8 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 @@ private static TypeDescriptor getTargetTypeDescriptor(PathInformation path) { 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 7999b98268..776b57ab38 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.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 @@ public void addIncludeFilter(TypeFilter includeFilter) { @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 @@ public void setConsiderNestedRepositoryInterfaces(boolean considerNestedReposito 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 @@ public boolean match(MetadataReader metadataReader, MetadataReaderFactory metada * * @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 21e4569135..136b9a12ad 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 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.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 @@ private static Environment defaultEnvironment(@Nullable Environment environment, return environment; } - return resourceLoader instanceof EnvironmentCapable capable ? capable.getEnvironment() - : new StandardEnvironment(); + return resourceLoader instanceof EnvironmentCapable capable ? capable.getEnvironment() : new StandardEnvironment(); } /** @@ -321,19 +320,19 @@ private static ApplicationStartup getStartup(BeanDefinitionRegistry registry) { 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 ddc85065ff..a2a2341f29 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 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 @@ private final DefaultQueryMethods calculateQueryMethods() { 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 @@ private final boolean calculateHasCustomMethod(Class repositoryInterface) { 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 9438a3fc92..966455cf44 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.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 b2b1a728f6..af1c6ad71e 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 @@ 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 void setNamedQueries(@Nullable NamedQueries namedQueries) { @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 T getRepository(Class repositoryInterface, RepositoryFragments fra 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 @@ private RepositoryInformation getRepositoryInformation(RepositoryMetadata metada 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 Object invoke(@SuppressWarnings("null") MethodInvocation invocation) thro 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 @@ static class RepositoryValidator { 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 89205e16e0..97eccfbd5f 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.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 5528c3e351..5a3f887694 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.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 QueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory 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 @@ private void validate() { 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 @@ static void validate(Method method) { 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 37fd9cad9b..a57502ed24 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.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 @@ * @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 static boolean hasProperty(Class type, String property) { * 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 static boolean isGenericRepositoryInterface(@Nullable String interfaceNam /** * @deprecated Use {@link #getNumberOfOccurrences(Method, Class)}. */ - @Deprecated public static int getNumberOfOccurences(Method method, Class type) { return getNumberOfOccurrences(method, type); } @@ -124,10 +115,7 @@ public static int getNumberOfOccurences(Method method, Class type) { * @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 static boolean isOfType(@Nullable Object object, Collection> typ * @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 static boolean hasParameterOfType(Method method, Class type) { * @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 static boolean hasParameterAssignableToType(Method method, Class type) */ 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 c7895ebbdf..a4f79ddf23 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 @@ public EvaluationContextExtensionInformation(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 bdc68bfb59..65352f6e4f 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 @@ private Mono> getExtensions( 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 3c2470d288..cf602a6ba6 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 @@ private 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 c9df96626c..902eb8a362 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 0000000000..eecaf4ae2f --- /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 2809f2ec39..fbee7fdaf4 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.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 @@ private 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 @@ private 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 interface DescribedFieldFilter extends FieldFilter { * @return */ String getDescription(); + } /** @@ -129,6 +178,7 @@ public boolean matches(Field field) { public String getDescription() { return String.format("Annotation filter for %s", annotationType.getName()); } + } /** @@ -151,6 +201,7 @@ public boolean matches(Field field) { public String getDescription() { return String.format("FieldFilter %s", filter.toString()); } + }, false); } @@ -200,7 +251,7 @@ public static Field findField(Class type, DescribedFieldFilter filter, boolea return field; } - if (foundField != null && enforceUniqueness) { + if (foundField != null) { throw new IllegalStateException(filter.getDescription()); } @@ -218,10 +269,25 @@ public static Field findField(Class type, DescribedFieldFilter filter, boolea * * @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 getRequiredField(Class type, String name) { Field result = org.springframework.util.ReflectionUtils.findField(type, name); @@ -251,7 +317,9 @@ public static void setField(Field field, Object target, @Nullable Object value) * @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 static Optional> findConstructor(Class type, Object... * @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 @@ private static boolean hasSameParams(Method method, Class[] paramTypes) { * 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 static Stream> returnTypeAndParameters(Method method) { * @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[0])); + + if (method != null) { - Method method = org.springframework.util.ReflectionUtils.findMethod(type, name, - collect.toArray(new Class[collect.size()])); + for (int i = 0; i < parameterTypes.length; i++) { + if (!ResolvableType.forMethodParameter(method, i).equals(parameterTypes[i])) { + return null; + } + } + + return method; + } - return Optional.ofNullable(method)// - .filter(it -> IntStream.range(0, it.getParameterCount())// - .allMatch(index -> ResolvableType.forMethodParameter(it, index).equals(parameterTypes[index]))); + return null; } private static boolean argumentsMatch(Class[] parameterTypes, Object[] arguments) { @@ -455,12 +566,9 @@ public static Object getPrimitiveDefault(Class type) { * @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 729ebb6a0b..2452494806 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) }