Introduce ParametersSource to Parameters.

We now provide a ParametersSource object to create MethodParameter objects associated with the enclosing class so parameters can resolve generics properly.

Closes #2996
This commit is contained in:
Mark Paluch
2023-12-04 11:01:37 +01:00
parent ba4526edb6
commit 52cdfe2631
7 changed files with 203 additions and 37 deletions

View File

@@ -18,8 +18,6 @@ package org.springframework.data.repository.query;
import java.lang.reflect.Method;
import java.util.List;
import org.springframework.data.util.TypeInformation;
/**
* Default implementation of {@link Parameters}.
*
@@ -31,7 +29,7 @@ public final class DefaultParameters extends Parameters<DefaultParameters, Param
* Creates a new {@link DefaultParameters} instance from the given {@link Method}.
*
* @param method must not be {@literal null}.
* @deprecated since 3.1, use {@link #DefaultParameters(Method, TypeInformation)} instead.
* @deprecated since 3.1, use {@link #DefaultParameters(ParametersSource)} instead.
*/
@Deprecated(since = "3.1", forRemoval = true)
public DefaultParameters(Method method) {
@@ -39,14 +37,13 @@ public final class DefaultParameters extends Parameters<DefaultParameters, Param
}
/**
* Creates a new {@link DefaultParameters} instance from the given {@link Method} and aggregate
* {@link TypeInformation}.
* Creates a new {@link DefaultParameters} instance from the given {@link ParametersSource}.
*
* @param method must not be {@literal null}.
* @param aggregateType must not be {@literal null}.
* @param parametersSource must not be {@literal null}.
* @since 3.2.1
*/
public DefaultParameters(Method method, TypeInformation<?> aggregateType) {
super(method, param -> new Parameter(param, aggregateType));
public DefaultParameters(ParametersSource parametersSource) {
super(parametersSource, param -> new Parameter(param, parametersSource.getDomainTypeInformation()));
}
private DefaultParameters(List<Parameter> parameters) {

View File

@@ -44,7 +44,8 @@ import org.springframework.util.Assert;
*/
public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter> implements Streamable<T> {
public static final List<Class<?>> TYPES = Arrays.asList(ScrollPosition.class, Pageable.class, Sort.class, Limit.class);
public static final List<Class<?>> TYPES = Arrays.asList(ScrollPosition.class, Pageable.class, Sort.class,
Limit.class);
private static final String PARAM_ON_SPECIAL = format("You must not use @%s on a parameter typed %s or %s",
Param.class.getSimpleName(), Pageable.class.getSimpleName(), Sort.class.getSimpleName());
@@ -82,14 +83,30 @@ public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter
* @param method must not be {@literal null}.
* @param parameterFactory must not be {@literal null}.
* @since 3.0.2
* @deprecated since 3.2.1, use {@link Parameters(ParametersSource, Function)} instead.
*/
@Deprecated(since = "3.2.1", forRemoval = true)
protected Parameters(Method method, Function<MethodParameter, T> parameterFactory) {
this(ParametersSource.of(method), parameterFactory);
}
Assert.notNull(method, "Method must not be null");
/**
* Creates a new {@link Parameters} instance for the given {@link Method} and {@link Function} to create a
* {@link Parameter} instance from a {@link MethodParameter}.
*
* @param parametersSource must not be {@literal null}.
* @param parameterFactory must not be {@literal null}.
* @since 3.2.1
*/
protected Parameters(ParametersSource parametersSource,
Function<MethodParameter, T> parameterFactory) {
Assert.notNull(parametersSource, "ParametersSource must not be null");
// Factory nullability not enforced yet to support falling back to the deprecated
// createParameter(MethodParameter). Add assertion when the deprecation is removed.
Method method = parametersSource.getMethod();
int parameterCount = method.getParameterCount();
this.parameters = new ArrayList<>(parameterCount);
@@ -102,7 +119,9 @@ public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter
for (int i = 0; i < parameterCount; i++) {
MethodParameter methodParameter = new MethodParameter(method, i);
MethodParameter methodParameter = new MethodParameter(method, i)
.withContainingClass(parametersSource.getContainingClass());
methodParameter.initParameterNameDiscovery(PARAMETER_NAME_DISCOVERER);
T parameter = parameterFactory == null //
@@ -421,7 +440,9 @@ public abstract class Parameters<S extends Parameters<S, T>, T extends Parameter
return !TYPES.contains(type);
}
@Override
public Iterator<T> iterator() {
return parameters.iterator();
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2023 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.query;
import java.lang.reflect.Method;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
/**
* Interface providing access to the method, containing class and domain type as source object for parameter
* descriptors.
*
* @author Mark Paluch
* @since 3.2.1
*/
public interface ParametersSource {
/**
* Create a new parameter source for the given {@link Method}.
*
* @param method the method to use.
* @return a new parameter source for the given {@link Method}.
*/
static ParametersSource of(Method method) {
Assert.notNull(method, "Method must not be null");
return new ParametersSource() {
@Override
public Class<?> getContainingClass() {
return method.getDeclaringClass();
}
@Override
public Method getMethod() {
return method;
}
@Override
public TypeInformation<?> getDomainTypeInformation() {
return TypeInformation.OBJECT;
}
};
}
/**
* Create a new parameter source for the given {@link Method} in the context of {@link RepositoryMetadata}.
*
* @param method the method to use.
* @return a new parameter source for the given {@link Method}.
*/
static ParametersSource of(RepositoryMetadata metadata, Method method) {
Assert.notNull(metadata, "RepositoryMetadata must not be null");
Assert.notNull(method, "Method must not be null");
return new ParametersSource() {
@Override
public Class<?> getContainingClass() {
return metadata.getRepositoryInterface();
}
@Override
public Method getMethod() {
return method;
}
@Override
public TypeInformation<?> getDomainTypeInformation() {
return metadata.getDomainTypeInformation();
}
};
}
/**
* @return the class that contains the {@link #getMethod()}.
*/
Class<?> getContainingClass();
/**
* @return the method for which the parameter source is defined.
*/
Method getMethod();
/**
* @return the domain type associated with the parameter source.
*/
TypeInformation<?> getDomainTypeInformation();
}

View File

@@ -183,16 +183,29 @@ public class QueryMethod {
* @param method must not be {@literal null}.
* @param domainType must not be {@literal null}.
* @return must not return {@literal null}.
* @deprecated since 3.2.1, use {@link #createParameters(ParametersSource)} instead.
* @since 3.0.2
*/
@Deprecated(since = "3.2.1", forRemoval = true)
protected Parameters<?, ?> createParameters(Method method, TypeInformation<?> domainType) {
return new DefaultParameters(method, domainType);
return createParameters(ParametersSource.of(getMetadata(), method));
}
/**
* Creates a {@link Parameters} instance.
*
* @param parametersSource must not be {@literal null}.
* @return must not return {@literal null}.
* @since 3.2.1
*/
protected Parameters<?, ?> createParameters(ParametersSource parametersSource) {
return new DefaultParameters(parametersSource);
}
/**
* Returns the method's name.
*
* @return
* @return the method's name.
*/
public String getName() {
return method.getName();