functions = new ArrayList<>(this.fragments);
+
+ if (customImplementation != null) {
+ functions.add(0, RepositoryFragmentsFunction.just(RepositoryFragments.just(customImplementation)));
+ }
+
+ FragmentCreationContext creationContext = new DefaultFragmentCreationContext(repositoryMetadata,
+ valueExpressionDelegate, factory.getProjectionFactory());
+
+ RepositoryFragments fragments = RepositoryFragments.empty();
+ for (RepositoryFragmentsFunction function : functions) {
+ fragments = fragments.append(function.getRepositoryFragments(this.beanFactory,
+ creationContext));
+ }
+
+ return fragments;
+ }
+
+ /**
+ * Functional interface to obtain {@link RepositoryFragments} for a given {@link BeanFactory} (can be
+ * {@literal null}), {@link EntityInformation} and {@link ValueExpressionDelegate}.
+ *
+ * This interface is used within the Framework and should not be used in application code.
+ *
+ * @since 4.0
+ */
+ public interface RepositoryFragmentsFunction {
+
+ /**
+ * Return {@link RepositoryFragments} for a given {@link BeanFactory} (can be {@literal null}),
+ * {@link EntityInformation} and {@link ValueExpressionDelegate}.
+ *
+ * @param beanFactory can be {@literal null}.
+ * @param context the creation context.
+ * @return the repository fragments to use.
+ */
+ RepositoryFragments getRepositoryFragments(@Nullable BeanFactory beanFactory,
+ FragmentCreationContext context);
+
+ /**
+ * Factory method to create {@link RepositoryFragmentsFunction} for a resolved {@link RepositoryFragments} object.
+ *
+ * @param fragments the fragments to use.
+ * @return a supplier {@link RepositoryFragmentsFunction} returning just {@code fragments}.
+ */
+ static RepositoryFragmentsFunction just(RepositoryFragments fragments) {
+ return (bf, context) -> fragments;
+ }
+
+ }
+
+ /**
+ * Creation context for a Repository Fragment.
+ *
+ * @since 4.0
+ */
+ public interface FragmentCreationContext {
+
+ /**
+ * @return the repository metadata in use.
+ */
+ RepositoryMetadata getRepositoryMetadata();
+
+ /**
+ * @return delegate for Value Expression parsing and evaluation.
+ */
+ ValueExpressionDelegate getValueExpressionDelegate();
+
+ /**
+ * @return the projection factory to use.
+ */
+ ProjectionFactory getProjectionFactory();
+
+ }
+
+ private record DefaultFragmentCreationContext(RepositoryMetadata repositoryMetadata,
+ ValueExpressionDelegate valueExpressionDelegate,
+ ProjectionFactory projectionFactory) implements FragmentCreationContext {
+
+ @Override
+ public RepositoryMetadata getRepositoryMetadata() {
+ return repositoryMetadata;
+ }
+
+ @Override
+ public ValueExpressionDelegate getValueExpressionDelegate() {
+ return valueExpressionDelegate();
+ }
+
+ @Override
+ public ProjectionFactory getProjectionFactory() {
+ return projectionFactory();
+ }
+
+ }
+
}
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 59b21e47a..88b4f11d8 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
@@ -122,7 +122,6 @@ public abstract class RepositoryFactorySupport
private @Nullable BeanFactory beanFactory;
private @Nullable Environment environment;
private Lazy projectionFactory;
- private @Nullable Object aotImplementation;
private final QueryCollectingQueryCreationListener collectingListener = new QueryCollectingQueryCreationListener();
@@ -268,10 +267,6 @@ public abstract class RepositoryFactorySupport
this.postProcessors.add(processor);
}
- public void setAotImplementation(@Nullable Object aotImplementation) {
- this.aotImplementation = aotImplementation;
- }
-
/**
* Creates {@link RepositoryFragments} based on {@link RepositoryMetadata} to add repository-specific extensions.
*
@@ -417,9 +412,9 @@ public abstract class RepositoryFactorySupport
}
Optional queryLookupStrategy = getQueryLookupStrategy(queryLookupStrategyKey,
- new ValueExpressionDelegate(
- new QueryMethodValueEvaluationContextAccessor(getEnvironment(), evaluationContextProvider), VALUE_PARSER));
- result.addAdvice(new QueryExecutorMethodInterceptor(information, getProjectionFactory(), queryLookupStrategy,
+ getValueExpressionDelegate());
+ result.addAdvice(new QueryExecutorMethodInterceptor(information, getProjectionFactory(),
+ queryLookupStrategy.orElse(null),
namedQueries, queryPostProcessors, methodInvocationListeners));
result.addAdvice(
@@ -437,6 +432,11 @@ public abstract class RepositoryFactorySupport
return repository;
}
+ ValueExpressionDelegate getValueExpressionDelegate() {
+ return new ValueExpressionDelegate(
+ new QueryMethodValueEvaluationContextAccessor(getEnvironment(), evaluationContextProvider), VALUE_PARSER);
+ }
+
/**
* Returns the {@link ProjectionFactory} to be used with the repository instances created.
*
@@ -504,10 +504,6 @@ public abstract class RepositoryFactorySupport
RepositoryComposition composition = RepositoryComposition.fromMetadata(metadata);
RepositoryFragments repositoryAspects = getRepositoryFragments(metadata);
- if(aotImplementation != null) {
- repositoryAspects = RepositoryFragments.just(aotImplementation).append(repositoryAspects);
- }
-
composition = composition.append(fragments).append(repositoryAspects);
Class> baseClass = repositoryBaseClass != null ? repositoryBaseClass : getRepositoryBaseClass(metadata);
diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFragment.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFragment.java
index 63013371a..f89b80b84 100644
--- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFragment.java
+++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFragment.java
@@ -16,7 +16,9 @@
package org.springframework.data.repository.core.support;
import java.lang.reflect.Method;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
@@ -108,6 +110,15 @@ public interface RepositoryFragment {
return Arrays.stream(getSignatureContributor().getMethods());
}
+ /**
+ * Find methods that match the given name. The method name must be exact.
+ *
+ * @param name the method name.
+ * @return list of candidate methods.
+ * @since 4.0
+ */
+ List findMethods(String name);
+
/**
* @return the class/interface providing signatures for this {@link RepositoryFragment}.
*/
@@ -122,12 +133,65 @@ public interface RepositoryFragment {
*/
RepositoryFragment withImplementation(T implementation);
+ private static List findMethods(String name, Method[] candidates) {
+
+ Method firstMatch = null;
+
+ for (Method method : candidates) {
+ if (method.getName().equals(name)) {
+
+ if (firstMatch != null) {
+ firstMatch = null;
+ break;
+ }
+ firstMatch = method;
+ }
+ }
+
+ if (firstMatch != null) {
+ return List.of(firstMatch);
+ }
+
+ List results = new ArrayList<>(candidates.length);
+
+ for (Method method : candidates) {
+ if (method.getName().equals(name)) {
+ results.add(method);
+ }
+ }
+
+ return results;
+ }
+
+ private static boolean hasMethod(Method method, Method[] candidates) {
+
+ for (Method candidate : candidates) {
+
+ if (!candidate.getName().equals(method.getName())) {
+ continue;
+ }
+
+ if (candidate.getParameterCount() != method.getParameterCount()) {
+ continue;
+
+ }
+
+ if (Arrays.equals(candidate.getParameterTypes(), method.getParameterTypes())) {
+ return true;
+ }
+
+ }
+ return false;
+ }
+
class StructuralRepositoryFragment implements RepositoryFragment {
private final Class interfaceOrImplementation;
+ private final Method[] methods;
public StructuralRepositoryFragment(Class interfaceOrImplementation) {
this.interfaceOrImplementation = interfaceOrImplementation;
+ this.methods = getSignatureContributor().getMethods();
}
@Override
@@ -135,6 +199,26 @@ public interface RepositoryFragment {
return interfaceOrImplementation;
}
+ @Override
+ public Stream methods() {
+ return Arrays.stream(methods);
+ }
+
+ @Override
+ public List findMethods(String name) {
+ return RepositoryFragment.findMethods(name, methods);
+ }
+
+ @Override
+ public boolean hasMethod(Method method) {
+
+ if (RepositoryFragment.hasMethod(method, this.methods)) {
+ return true;
+ }
+
+ return RepositoryFragment.super.hasMethod(method);
+ }
+
@Override
public RepositoryFragment withImplementation(T implementation) {
return new ImplementedRepositoryFragment<>(interfaceOrImplementation, implementation);
@@ -169,6 +253,7 @@ public interface RepositoryFragment {
private final @Nullable Class interfaceClass;
private final T implementation;
+ private final Method[] methods;
/**
* Creates a new {@link ImplementedRepositoryFragment} for the given interface class and implementation.
@@ -182,14 +267,16 @@ public interface RepositoryFragment {
if (interfaceClass != null) {
- Assert.isTrue(ClassUtils.isAssignableValue(interfaceClass, implementation),
- () -> "Fragment implementation %s does not implement %s".formatted(
- ClassUtils.getQualifiedName(implementation.getClass()),
- ClassUtils.getQualifiedName(interfaceClass)));
+ Assert
+ .isTrue(ClassUtils.isAssignableValue(interfaceClass, implementation),
+ () -> "Fragment implementation %s does not implement %s".formatted(
+ ClassUtils.getQualifiedName(implementation.getClass()),
+ ClassUtils.getQualifiedName(interfaceClass)));
}
this.interfaceClass = interfaceClass;
this.implementation = implementation;
+ this.methods = getSignatureContributor().getMethods();
}
@Override
@@ -205,6 +292,26 @@ public interface RepositoryFragment {
return implementation.getClass();
}
+ @Override
+ public Stream methods() {
+ return Arrays.stream(methods);
+ }
+
+ @Override
+ public List findMethods(String name) {
+ return RepositoryFragment.findMethods(name, this.methods);
+ }
+
+ @Override
+ public boolean hasMethod(Method method) {
+
+ if (RepositoryFragment.hasMethod(method, this.methods)) {
+ return true;
+ }
+
+ return RepositoryFragment.super.hasMethod(method);
+ }
+
@Override
public Optional getImplementation() {
return Optional.of(implementation);
diff --git a/src/main/java/org/springframework/data/util/ReactiveWrappers.java b/src/main/java/org/springframework/data/util/ReactiveWrappers.java
index f82d575ef..87a1c75a6 100644
--- a/src/main/java/org/springframework/data/util/ReactiveWrappers.java
+++ b/src/main/java/org/springframework/data/util/ReactiveWrappers.java
@@ -18,6 +18,7 @@ package org.springframework.data.util;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
+import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
@@ -139,9 +140,20 @@ public abstract class ReactiveWrappers {
Assert.notNull(type, "Type must not be null");
- return Arrays.stream(type.getMethods())//
- .flatMap(ReflectionUtils::returnTypeAndParameters)//
- .anyMatch(ReactiveWrappers::supports);
+ for (Method method : type.getMethods()) {
+
+ if (ReactiveWrappers.supports(method.getReturnType())) {
+ return true;
+ }
+
+ for (Class> parameterType : method.getParameterTypes()) {
+ if (ReactiveWrappers.supports(parameterType)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
}
/**
diff --git a/src/main/java/org/springframework/data/util/TypeDiscoverer.java b/src/main/java/org/springframework/data/util/TypeDiscoverer.java
index 48c75e0ce..4edf078b6 100644
--- a/src/main/java/org/springframework/data/util/TypeDiscoverer.java
+++ b/src/main/java/org/springframework/data/util/TypeDiscoverer.java
@@ -200,6 +200,11 @@ class TypeDiscoverer implements TypeInformation {
return new TypeDescriptor(resolvableType, getType(), null);
}
+ @Override
+ public ResolvableType toResolvableType() {
+ return resolvableType;
+ }
+
@Override
public TypeInformation> getRawTypeInformation() {
return new ClassTypeInformation<>(ResolvableType.forRawClass(resolvableType.toClass()));
diff --git a/src/main/java/org/springframework/data/util/TypeInformation.java b/src/main/java/org/springframework/data/util/TypeInformation.java
index 175ed92ce..ed12ef802 100644
--- a/src/main/java/org/springframework/data/util/TypeInformation.java
+++ b/src/main/java/org/springframework/data/util/TypeInformation.java
@@ -382,4 +382,13 @@ public interface TypeInformation {
* @since 2.7
*/
TypeDescriptor toTypeDescriptor();
+
+ /**
+ * Returns the {@link ResolvableType} for this type information.
+ *
+ * @return will never be {@literal null}.
+ * @since 4.0
+ */
+ ResolvableType toResolvableType();
+
}
diff --git a/src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotProcessorIntegrationTests.java b/src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotProcessorIntegrationTests.java
index 3f2439434..39bc54554 100644
--- a/src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotProcessorIntegrationTests.java
+++ b/src/test/java/org/springframework/data/repository/aot/RepositoryRegistrationAotProcessorIntegrationTests.java
@@ -21,6 +21,7 @@ import static org.springframework.data.repository.aot.RepositoryRegistrationAotC
import java.io.Serializable;
import org.junit.jupiter.api.Test;
+
import org.springframework.aop.SpringProxy;
import org.springframework.aop.framework.Advised;
import org.springframework.aot.hint.RuntimeHints;
@@ -31,9 +32,18 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.DecoratingProxy;
-import org.springframework.data.aot.sample.*;
+import org.springframework.data.aot.sample.ConfigWithCustomImplementation;
+import org.springframework.data.aot.sample.ConfigWithCustomRepositoryBaseClass;
+import org.springframework.data.aot.sample.ConfigWithFragments;
+import org.springframework.data.aot.sample.ConfigWithQueryMethods;
import org.springframework.data.aot.sample.ConfigWithQueryMethods.ProjectionInterface;
+import org.springframework.data.aot.sample.ConfigWithQuerydslPredicateExecutor;
import org.springframework.data.aot.sample.ConfigWithQuerydslPredicateExecutor.Person;
+import org.springframework.data.aot.sample.ConfigWithSimpleCrudRepository;
+import org.springframework.data.aot.sample.ConfigWithTransactionManagerPresent;
+import org.springframework.data.aot.sample.ConfigWithTransactionManagerPresentAndAtComponentAnnotatedRepository;
+import org.springframework.data.aot.sample.QConfigWithQuerydslPredicateExecutor_Person;
+import org.springframework.data.aot.sample.ReactiveConfig;
import org.springframework.data.domain.AbstractAggregateRoot;
import org.springframework.data.domain.AfterDomainEventPublication;
import org.springframework.data.domain.DomainEvents;
@@ -185,10 +195,8 @@ public class RepositoryRegistrationAotProcessorIntegrationTests {
.contributesReflectionFor(PagingAndSortingRepository.class) // base repository
.contributesReflectionFor(ConfigWithCustomImplementation.Person.class) // repository domain type
- // fragments
- .contributesReflectionFor(ConfigWithCustomImplementation.CustomImplInterface.class,
- ConfigWithCustomImplementation.RepositoryWithCustomImplementationImpl.class);
-
+ // fragments (custom implementation)
+ .contributesReflectionFor(ConfigWithCustomImplementation.RepositoryWithCustomImplementationImpl.class);
});
}
diff --git a/src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java b/src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java
index ebd9bd48e..3cfa07fe5 100644
--- a/src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java
@@ -15,15 +15,22 @@
*/
package org.springframework.data.repository.aot.generate;
-import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.*;
import example.UserRepository;
+import java.lang.reflect.Method;
+
+import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
+
import org.springframework.aot.test.generate.TestGenerationContext;
-import org.springframework.core.test.tools.ResourceFile;
import org.springframework.core.test.tools.TestCompiler;
import org.springframework.data.aot.CodeContributionAssert;
+import org.springframework.data.repository.core.RepositoryInformation;
+import org.springframework.data.repository.query.QueryMethod;
+import org.springframework.javapoet.CodeBlock;
+import org.springframework.util.ClassUtils;
/**
* @author Christoph Strobl
@@ -35,14 +42,21 @@ class RepositoryContributorUnitTests {
DummyModuleAotRepositoryContext aotContext = new DummyModuleAotRepositoryContext(UserRepository.class, null);
RepositoryContributor repositoryContributor = new RepositoryContributor(aotContext) {
- @Override
- protected AotRepositoryMethodBuilder contributeRepositoryMethod(AotRepositoryMethodGenerationContext context) {
- return new AotRepositoryMethodBuilder(context).customize(((ctx, builder) -> {
- if (!ctx.returnsVoid()) {
- builder.addStatement("return null");
- }
- }));
+ @Override
+ protected @Nullable MethodContributor extends QueryMethod> contributeQueryMethod(Method method,
+ RepositoryInformation repositoryInformation) {
+
+ return MethodContributor.forQueryMethod(new QueryMethod(method, repositoryInformation, getProjectionFactory()))
+ .contribute(context -> {
+
+ CodeBlock.Builder builder = CodeBlock.builder();
+ if (!ClassUtils.isVoidType(method.getReturnType())) {
+ builder.addStatement("return null");
+ }
+
+ return builder.build();
+ });
}
};
diff --git a/src/test/java/org/springframework/data/repository/aot/generate/StubRepositoryInformation.java b/src/test/java/org/springframework/data/repository/aot/generate/StubRepositoryInformation.java
index e526a2127..f117d9576 100644
--- a/src/test/java/org/springframework/data/repository/aot/generate/StubRepositoryInformation.java
+++ b/src/test/java/org/springframework/data/repository/aot/generate/StubRepositoryInformation.java
@@ -16,6 +16,7 @@
package org.springframework.data.repository.aot.generate;
import java.lang.reflect.Method;
+import java.util.List;
import java.util.Set;
import org.springframework.data.repository.core.CrudMethods;
@@ -24,7 +25,6 @@ import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.AbstractRepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryComposition;
import org.springframework.data.repository.core.support.RepositoryFragment;
-import org.springframework.data.util.Streamable;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
@@ -111,7 +111,7 @@ class StubRepositoryInformation implements RepositoryInformation {
}
@Override
- public Streamable getQueryMethods() {
+ public List getQueryMethods() {
return null;
}
diff --git a/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryInformation.java b/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryInformation.java
index 8eefb2928..5d19910fc 100644
--- a/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryInformation.java
+++ b/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryInformation.java
@@ -17,12 +17,12 @@ package org.springframework.data.repository.core.support;
import java.lang.reflect.Method;
import java.util.Collections;
+import java.util.List;
import java.util.Set;
import org.springframework.data.repository.core.CrudMethods;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
-import org.springframework.data.util.Streamable;
import org.springframework.data.util.TypeInformation;
public final class DummyRepositoryInformation implements RepositoryInformation {
@@ -83,8 +83,8 @@ public final class DummyRepositoryInformation implements RepositoryInformation {
}
@Override
- public Streamable getQueryMethods() {
- return Streamable.empty();
+ public List getQueryMethods() {
+ return Collections.emptyList();
}
@Override
diff --git a/src/test/java/org/springframework/data/repository/core/support/QueryExecutorMethodInterceptorUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/QueryExecutorMethodInterceptorUnitTests.java
index 082292c59..4d2cc8695 100755
--- a/src/test/java/org/springframework/data/repository/core/support/QueryExecutorMethodInterceptorUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/core/support/QueryExecutorMethodInterceptorUnitTests.java
@@ -19,12 +19,12 @@ import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.Collections;
-import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
+
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.query.QueryLookupStrategy;
@@ -49,13 +49,13 @@ class QueryExecutorMethodInterceptorUnitTests {
assertThatIllegalStateException()
.isThrownBy(() -> new QueryExecutorMethodInterceptor(information, new SpelAwareProxyProjectionFactory(),
- Optional.empty(), PropertiesBasedNamedQueries.EMPTY, Collections.emptyList(), Collections.emptyList()));
+ null, PropertiesBasedNamedQueries.EMPTY, Collections.emptyList(), Collections.emptyList()));
}
@Test // DATACMNS-1508
void skipsQueryLookupsIfQueryLookupStrategyIsNotPresent() {
- new QueryExecutorMethodInterceptor(information, new SpelAwareProxyProjectionFactory(), Optional.empty(),
+ new QueryExecutorMethodInterceptor(information, new SpelAwareProxyProjectionFactory(), null,
PropertiesBasedNamedQueries.EMPTY, Collections.emptyList(), Collections.emptyList());
verify(strategy, times(0)).resolveQuery(any(), any(), any(), any());