diff --git a/src/main/java/org/springframework/data/repository/core/RepositoryInformation.java b/src/main/java/org/springframework/data/repository/core/RepositoryInformation.java index d8b5067204..3131b21567 100644 --- a/src/main/java/org/springframework/data/repository/core/RepositoryInformation.java +++ b/src/main/java/org/springframework/data/repository/core/RepositoryInformation.java @@ -23,6 +23,7 @@ * Additional repository specific information * * @author Oliver Gierke + * @author Yanming Zhou */ public interface RepositoryInformation extends RepositoryMetadata { @@ -51,6 +52,16 @@ public interface RepositoryInformation extends RepositoryMetadata { */ boolean isQueryMethod(Method method); + /** + * Returns whether the given method is a lookup method. + * + * @param method + * @return + */ + default boolean isLookupMethod(Method method) { + return false; + } + /** * Returns all methods considered to be query methods. * 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 b89f6729f3..fb27672222 100644 --- a/src/main/java/org/springframework/data/repository/core/RepositoryInformationSupport.java +++ b/src/main/java/org/springframework/data/repository/core/RepositoryInformationSupport.java @@ -24,6 +24,7 @@ import java.util.Set; import java.util.function.Supplier; +import org.springframework.beans.factory.annotation.Lookup; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.annotation.QueryAnnotation; import org.springframework.data.util.Lazy; @@ -37,6 +38,7 @@ * repository base to the latest possible time. * * @author Christoph Strobl + * @author Yanming Zhou * @since 3.0 */ public abstract class RepositoryInformationSupport implements RepositoryInformation { @@ -127,6 +129,11 @@ public boolean isQueryMethod(Method method) { return getQueryMethods().stream().anyMatch(it -> it.equals(method)); } + @Override + public boolean isLookupMethod(Method method) { + return AnnotationUtils.findAnnotation(method, Lookup.class) != null; + } + @Override public TypeInformation getDomainTypeInformation() { return getMetadata().getDomainTypeInformation(); @@ -176,6 +183,7 @@ protected boolean isQueryAnnotationPresentOn(Method method) { protected boolean isQueryMethodCandidate(Method method) { return !method.isBridge() && !method.isDefault() // && !Modifier.isStatic(method.getModifiers()) // + && !isLookupMethod(method) // && (isQueryAnnotationPresentOn(method) || !isCustomMethod(method) && !isBaseClassMethod(method)); } } diff --git a/src/main/java/org/springframework/data/repository/core/support/QueryExecutorMethodInterceptor.java b/src/main/java/org/springframework/data/repository/core/support/QueryExecutorMethodInterceptor.java index d075596775..c0efa7560e 100644 --- a/src/main/java/org/springframework/data/repository/core/support/QueryExecutorMethodInterceptor.java +++ b/src/main/java/org/springframework/data/repository/core/support/QueryExecutorMethodInterceptor.java @@ -24,6 +24,7 @@ import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; +import org.springframework.beans.factory.BeanFactory; import org.springframework.core.ResolvableType; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.NamedQueries; @@ -49,6 +50,7 @@ * @author Christoph Strobl * @author John Blum * @author Johannes Englmeier + * @author Yanming Zhou */ class QueryExecutorMethodInterceptor implements MethodInterceptor { @@ -59,16 +61,18 @@ class QueryExecutorMethodInterceptor implements MethodInterceptor { private final NamedQueries namedQueries; private final List> queryPostProcessors; private final RepositoryInvocationMulticaster invocationMulticaster; + private final BeanFactory beanFactory; /** * Creates a new {@link QueryExecutorMethodInterceptor}. Builds a model of {@link QueryMethod}s to be invoked on * execution of repository interface methods. */ - public QueryExecutorMethodInterceptor(RepositoryInformation repositoryInformation, + public QueryExecutorMethodInterceptor(BeanFactory beanFactory, RepositoryInformation repositoryInformation, ProjectionFactory projectionFactory, Optional queryLookupStrategy, NamedQueries namedQueries, List> queryPostProcessors, List methodInvocationListeners) { + this.beanFactory = beanFactory; this.repositoryInformation = repositoryInformation; this.namedQueries = namedQueries; this.queryPostProcessors = queryPostProcessors; @@ -136,6 +140,10 @@ public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) thro Method method = invocation.getMethod(); + if (repositoryInformation.isLookupMethod(method)) { + return beanFactory.getBeanProvider(ResolvableType.forMethodReturnType(method)).getObject(invocation.getArguments()); + } + QueryExecutionConverters.ExecutionAdapter executionAdapter = QueryExecutionConverters // .getExecutionAdapter(method.getReturnType()); 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 9b295a6827..7a00742fa9 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 @@ -81,6 +81,7 @@ * @author Jens Schauder * @author John Blum * @author Johannes Englmeier + * @author Yanming Zhou */ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, BeanFactoryAware { @@ -354,7 +355,7 @@ public T getRepository(Class repositoryInterface, RepositoryFragments fra Optional queryLookupStrategy = getQueryLookupStrategy(queryLookupStrategyKey, evaluationContextProvider); - result.addAdvice(new QueryExecutorMethodInterceptor(information, getProjectionFactory(), queryLookupStrategy, + result.addAdvice(new QueryExecutorMethodInterceptor(beanFactory, information, getProjectionFactory(), queryLookupStrategy, namedQueries, queryPostProcessors, methodInvocationListeners)); result.addAdvice( diff --git a/src/test/java/org/springframework/data/repository/config/LookupRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/repository/config/LookupRepositoryIntegrationTests.java new file mode 100644 index 0000000000..4ed2ac0efe --- /dev/null +++ b/src/test/java/org/springframework/data/repository/config/LookupRepositoryIntegrationTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2019-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.config; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Lookup; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.data.mapping.Person; +import org.springframework.data.repository.CrudRepository; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for @Lookup. + * + * @author Yanming Zhou + */ +class LookupRepositoryIntegrationTests { + + @Test + void lookupBeansFromBeanFactory() { + + var context = new AnnotationConfigApplicationContext(Config.class); + var repository = context.getBean(PersonRepository.class); + assertThat(repository.getSelf()).isEqualTo(repository); + } + + @Configuration + @EnableRepositories(considerNestedRepositories = true, // + includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = PersonRepository.class)) + static class Config {} + + + interface PersonRepository extends CrudRepository { + + @Lookup + PersonRepository getSelf(); + } + +} diff --git a/src/test/java/org/springframework/data/repository/core/support/DefaultRepositoryInformationUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/DefaultRepositoryInformationUnitTests.java index 64a97debaf..3f5581f3d7 100755 --- a/src/test/java/org/springframework/data/repository/core/support/DefaultRepositoryInformationUnitTests.java +++ b/src/test/java/org/springframework/data/repository/core/support/DefaultRepositoryInformationUnitTests.java @@ -34,6 +34,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import org.springframework.beans.factory.annotation.Lookup; import org.springframework.data.annotation.QueryAnnotation; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -52,6 +53,7 @@ * @author Thomas Darimont * @author Mark Paluch * @author Johannes Englmeier + * @author Yanming Zhou */ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) @@ -272,6 +274,17 @@ void discoversCustomlyImplementedCrudMethodWithoutGenericParameters() throws Exc assertThat(information.isCustomMethod(customBaseRepositoryMethod)).isTrue(); } + @Test + void discoversLookupMethods() throws Exception { + + RepositoryMetadata metadata = new DefaultRepositoryMetadata(DummyRepository.class); + RepositoryInformation information = new DefaultRepositoryInformation(metadata, RepositoryFactorySupport.class, + RepositoryComposition.empty()); + + var lookupMethod = DummyRepository.class.getMethod("getSelf"); + assertThat(information.isLookupMethod(lookupMethod)).isTrue(); + } + private static Method getMethodFrom(Class type, String name) { return Arrays.stream(type.getMethods())// @@ -392,6 +405,9 @@ interface DummyRepository extends CrudRepository { @Override List saveAll(Iterable entites); + + @Lookup + DummyRepository getSelf(); } static class DummyRepositoryImpl implements CrudRepository { 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 281182a1b4..6306619d60 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 @@ -25,6 +25,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.query.QueryLookupStrategy; @@ -35,6 +37,7 @@ * @author Oliver Gierke * @author Mark Paluch * @author Jens Schauder + * @author Yanming Zhou */ @ExtendWith(MockitoExtension.class) class QueryExecutorMethodInterceptorUnitTests { @@ -42,20 +45,23 @@ class QueryExecutorMethodInterceptorUnitTests { @Mock RepositoryInformation information; @Mock QueryLookupStrategy strategy; + @Autowired + BeanFactory beanFactory; + @Test // DATACMNS-1508 void rejectsRepositoryInterfaceWithQueryMethodsIfNoQueryLookupStrategyIsDefined() { when(information.hasQueryMethods()).thenReturn(true); assertThatIllegalStateException() - .isThrownBy(() -> new QueryExecutorMethodInterceptor(information, new SpelAwareProxyProjectionFactory(), + .isThrownBy(() -> new QueryExecutorMethodInterceptor(beanFactory, information, new SpelAwareProxyProjectionFactory(), Optional.empty(), PropertiesBasedNamedQueries.EMPTY, Collections.emptyList(), Collections.emptyList())); } @Test // DATACMNS-1508 void skipsQueryLookupsIfQueryLookupStrategyIsNotPresent() { - new QueryExecutorMethodInterceptor(information, new SpelAwareProxyProjectionFactory(), Optional.empty(), + new QueryExecutorMethodInterceptor(beanFactory, information, new SpelAwareProxyProjectionFactory(), Optional.empty(), PropertiesBasedNamedQueries.EMPTY, Collections.emptyList(), Collections.emptyList()); verify(strategy, times(0)).resolveQuery(any(), any(), any(), any());