Skip to content

Commit d2ca1fc

Browse files
Refine Repository Composition retrieval during AOT.
Add module identifier and base repository implementation properties. Fix fragment function previously overriding already set property due to name clash. Extend tests for bean definition resolution and code block creation. See: #3279 Original Pull Request: #3282
1 parent 5c68547 commit d2ca1fc

18 files changed

+800
-73
lines changed

src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java

+3-4
Original file line numberDiff line numberDiff line change
@@ -138,24 +138,23 @@ public AotBundle build() {
138138
this.customizer.customize(repositoryInformation, generationMetadata, builder);
139139
JavaFile javaFile = JavaFile.builder(packageName(), builder.build()).build();
140140

141-
// TODO: module identifier
142141
AotRepositoryMetadata metadata = new AotRepositoryMetadata(repositoryInformation.getRepositoryInterface().getName(),
143-
"", repositoryType, methodMetadata);
142+
repositoryInformation.moduleName() != null ? repositoryInformation.moduleName() : "", repositoryType, methodMetadata);
144143

145144
return new AotBundle(javaFile, metadata.toJson());
146145
}
147146

148147
private void contributeMethod(Method method, RepositoryComposition repositoryComposition,
149148
List<AotRepositoryMethod> methodMetadata, TypeSpec.Builder builder) {
150149

151-
if (repositoryInformation.isCustomMethod(method) || repositoryInformation.isBaseClassMethod(method)) {
150+
if (repositoryInformation.isCustomMethod(method) || (repositoryInformation.isBaseClassMethod(method) && !repositoryInformation.isQueryMethod(method))) {
152151

153152
RepositoryFragment<?> fragment = repositoryComposition.findFragment(method);
154153

155154
if (fragment != null) {
156155
methodMetadata.add(getFragmentMetadata(method, fragment));
156+
return;
157157
}
158-
return;
159158
}
160159

161160
if (method.isBridge() || method.isDefault() || java.lang.reflect.Modifier.isStatic(method.getModifiers())) {

src/main/java/org/springframework/data/repository/aot/generate/MethodContributor.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public abstract class MethodContributor<M extends QueryMethod> {
3636
private final M queryMethod;
3737
private final QueryMetadata metadata;
3838

39-
private MethodContributor(M queryMethod, QueryMetadata metadata) {
39+
MethodContributor(M queryMethod, QueryMetadata metadata) {
4040
this.queryMethod = queryMethod;
4141
this.metadata = metadata;
4242
}

src/main/java/org/springframework/data/repository/config/AotRepositoryBeanDefinitionPropertiesDecorator.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public CodeBlock decorate() {
5555
// bring in properties as usual
5656
builder.add(inheritedProperties.get());
5757

58-
builder.add("beanDefinition.getPropertyValues().addPropertyValue(\"repositoryFragments\", new $T() {\n",
58+
builder.add("beanDefinition.getPropertyValues().addPropertyValue(\"repositoryFragmentsFunction\", new $T() {\n",
5959
RepositoryFactoryBeanSupport.RepositoryFragmentsFunction.class);
6060
builder.indent();
6161
builder.add("public $T getRepositoryFragments($T beanFactory, $T context) {\n",

src/main/java/org/springframework/data/repository/config/AotRepositoryInformation.java

+28-8
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@
2121
import java.util.Set;
2222
import java.util.function.Supplier;
2323

24+
import org.jspecify.annotations.Nullable;
2425
import org.springframework.data.repository.core.RepositoryInformation;
2526
import org.springframework.data.repository.core.RepositoryInformationSupport;
2627
import org.springframework.data.repository.core.RepositoryMetadata;
2728
import org.springframework.data.repository.core.support.RepositoryComposition;
29+
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
2830
import org.springframework.data.repository.core.support.RepositoryFragment;
2931
import org.springframework.data.util.Lazy;
3032

@@ -36,16 +38,31 @@
3638
*/
3739
class AotRepositoryInformation extends RepositoryInformationSupport implements RepositoryInformation {
3840

41+
private final @Nullable String moduleName;
3942
private final Supplier<Collection<RepositoryFragment<?>>> fragments;
40-
private Lazy<RepositoryComposition> baseComposition = Lazy.of(() -> {
41-
return RepositoryComposition.of(RepositoryFragment.structural(getRepositoryBaseClass()));
42-
});
4343

44-
AotRepositoryInformation(Supplier<RepositoryMetadata> repositoryMetadata, Supplier<Class<?>> repositoryBaseClass,
45-
Supplier<Collection<RepositoryFragment<?>>> fragments) {
44+
private final Lazy<RepositoryComposition> repositoryComposition;
45+
private final Lazy<RepositoryComposition> baseComposition;
46+
47+
AotRepositoryInformation(@Nullable String moduleName, Supplier<RepositoryMetadata> repositoryMetadata,
48+
Supplier<Class<?>> repositoryBaseClass, Supplier<Collection<RepositoryFragment<?>>> fragments) {
4649

4750
super(repositoryMetadata, repositoryBaseClass);
51+
52+
this.moduleName = moduleName;
4853
this.fragments = fragments;
54+
55+
this.repositoryComposition = Lazy
56+
.of(() -> RepositoryComposition.fromMetadata(getMetadata()).append(RepositoryFragments.from(getFragments())));
57+
58+
this.baseComposition = Lazy.of(() -> {
59+
60+
RepositoryComposition targetRepoComposition = repositoryComposition.get();
61+
62+
return RepositoryComposition.of(RepositoryFragment.structural(getRepositoryBaseClass())) //
63+
.withArgumentConverter(targetRepoComposition.getArgumentConverter()) //
64+
.withMethodLookup(targetRepoComposition.getMethodLookup());
65+
});
4966
}
5067

5168
/**
@@ -57,10 +74,9 @@ public Set<RepositoryFragment<?>> getFragments() {
5774
return new LinkedHashSet<>(fragments.get());
5875
}
5976

60-
// Not required during AOT processing.
6177
@Override
6278
public boolean isCustomMethod(Method method) {
63-
return false;
79+
return repositoryComposition.get().findMethod(method).isPresent();
6480
}
6581

6682
@Override
@@ -75,7 +91,11 @@ public Method getTargetClassMethod(Method method) {
7591

7692
@Override
7793
public RepositoryComposition getRepositoryComposition() {
78-
return baseComposition.get().append(RepositoryComposition.RepositoryFragments.from(fragments.get()));
94+
return repositoryComposition.get();
7995
}
8096

97+
@Override
98+
public @Nullable String moduleName() {
99+
return moduleName;
100+
}
81101
}

src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionReader.java

+98-35
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,21 @@
1616
package org.springframework.data.repository.config;
1717

1818
import java.util.ArrayList;
19-
import java.util.Collection;
20-
import java.util.Collections;
2119
import java.util.List;
22-
import java.util.function.Supplier;
23-
import java.util.stream.Collectors;
2420

21+
import org.springframework.beans.factory.config.BeanDefinition;
2522
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
23+
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
24+
import org.springframework.beans.factory.config.RuntimeBeanReference;
25+
import org.springframework.beans.factory.support.RegisteredBean;
26+
import org.springframework.core.ResolvableType;
27+
import org.springframework.data.repository.CrudRepository;
28+
import org.springframework.data.repository.PagingAndSortingRepository;
2629
import org.springframework.data.repository.core.RepositoryInformation;
27-
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
30+
import org.springframework.data.repository.core.RepositoryMetadata;
31+
import org.springframework.data.repository.core.support.AbstractRepositoryMetadata;
2832
import org.springframework.data.repository.core.support.RepositoryFragment;
29-
import org.springframework.data.util.Lazy;
33+
import org.springframework.data.repository.core.support.RepositoryFragment.ImplementedRepositoryFragment;
3034
import org.springframework.util.ClassUtils;
3135

3236
/**
@@ -38,49 +42,108 @@
3842
*/
3943
class RepositoryBeanDefinitionReader {
4044

41-
static RepositoryInformation readRepositoryInformation(RepositoryConfiguration<?> metadata,
42-
ConfigurableListableBeanFactory beanFactory) {
43-
44-
return new AotRepositoryInformation(metadataSupplier(metadata, beanFactory),
45-
repositoryBaseClass(metadata, beanFactory), fragments(metadata, beanFactory));
45+
/**
46+
* @return
47+
*/
48+
static RepositoryInformation repositoryInformation(RepositoryConfiguration<?> repoConfig, RegisteredBean repoBean) {
49+
return repositoryInformation(repoConfig, repoBean.getMergedBeanDefinition(), repoBean.getBeanFactory());
4650
}
4751

48-
private static Supplier<Collection<RepositoryFragment<?>>> fragments(RepositoryConfiguration<?> metadata,
52+
/**
53+
* @param source the RepositoryFactoryBeanSupport bean definition.
54+
* @param beanFactory
55+
* @return
56+
*/
57+
@SuppressWarnings("NullAway")
58+
static RepositoryInformation repositoryInformation(RepositoryConfiguration<?> repoConfig, BeanDefinition source,
4959
ConfigurableListableBeanFactory beanFactory) {
5060

51-
if (metadata instanceof RepositoryFragmentConfigurationProvider provider) {
52-
53-
return Lazy.of(() -> {
54-
return provider.getFragmentConfiguration().stream().flatMap(it -> {
55-
56-
List<RepositoryFragment<?>> fragments = new ArrayList<>(1);
61+
RepositoryMetadata metadata = AbstractRepositoryMetadata
62+
.getMetadata(forName(repoConfig.getRepositoryInterface(), beanFactory));
63+
Class<?> repositoryBaseClass = readRepositoryBaseClass(source, beanFactory);
64+
List<RepositoryFragment<?>> fragmentList = readRepositoryFragments(source, beanFactory);
65+
if (source.getPropertyValues().contains("customImplementation")) {
66+
67+
Object o = source.getPropertyValues().get("customImplementation");
68+
if (o instanceof RuntimeBeanReference rbr) {
69+
BeanDefinition customImplBeanDefintion = beanFactory.getBeanDefinition(rbr.getBeanName());
70+
Class<?> beanType = forName(customImplBeanDefintion.getBeanClassName(), beanFactory);
71+
ResolvableType[] interfaces = ResolvableType.forClass(beanType).getInterfaces();
72+
if (interfaces.length == 1) {
73+
fragmentList.add(new ImplementedRepositoryFragment(interfaces[0].toClass(), beanType));
74+
} else {
75+
boolean found = false;
76+
for (ResolvableType i : interfaces) {
77+
if (beanType.getSimpleName().contains(i.resolve().getSimpleName())) {
78+
fragmentList.add(new ImplementedRepositoryFragment(interfaces[0].toClass(), beanType));
79+
found = true;
80+
break;
81+
}
82+
}
83+
if (!found) {
84+
fragmentList.add(RepositoryFragment.implemented(beanType));
85+
}
86+
}
87+
}
88+
}
5789

58-
fragments.add(RepositoryFragment.implemented(forName(it.getClassName(), beanFactory)));
90+
String moduleName = (String) source.getPropertyValues().get("moduleName");
91+
AotRepositoryInformation repositoryInformation = new AotRepositoryInformation(moduleName, () -> metadata,
92+
() -> repositoryBaseClass, () -> fragmentList);
93+
return repositoryInformation;
94+
}
5995

60-
if (it.getInterfaceName() != null) {
61-
fragments.add(RepositoryFragment.structural(forName(it.getInterfaceName(), beanFactory)));
62-
}
96+
@SuppressWarnings("NullAway")
97+
private static Class<?> readRepositoryBaseClass(BeanDefinition source, ConfigurableListableBeanFactory beanFactory) {
6398

64-
return fragments.stream();
65-
}).collect(Collectors.toList());
66-
});
99+
Object repoBaseClassName = source.getPropertyValues().get("repositoryBaseClass");
100+
if (repoBaseClassName != null) {
101+
return forName(repoBaseClassName.toString(), beanFactory);
67102
}
68-
69-
return Lazy.of(Collections::emptyList);
103+
if (source.getPropertyValues().contains("moduleBaseClass")) {
104+
return forName((String) source.getPropertyValues().get("moduleBaseClass"), beanFactory);
105+
}
106+
return Dummy.class;
70107
}
71108

72-
@SuppressWarnings({ "rawtypes", "unchecked" })
73-
private static Supplier<Class<?>> repositoryBaseClass(RepositoryConfiguration metadata,
109+
@SuppressWarnings("NullAway")
110+
private static List<RepositoryFragment<?>> readRepositoryFragments(BeanDefinition source,
74111
ConfigurableListableBeanFactory beanFactory) {
75112

76-
return Lazy.of(() -> (Class<?>) metadata.getRepositoryBaseClassName().map(it -> forName(it.toString(), beanFactory))
77-
.orElse(Object.class));
113+
RuntimeBeanReference beanReference = (RuntimeBeanReference) source.getPropertyValues().get("repositoryFragments");
114+
BeanDefinition fragments = beanFactory.getBeanDefinition(beanReference.getBeanName());
115+
116+
ValueHolder fragmentBeanNameList = fragments.getConstructorArgumentValues().getArgumentValue(0, List.class);
117+
List<String> fragmentBeanNames = (List<String>) fragmentBeanNameList.getValue();
118+
119+
List<RepositoryFragment<?>> fragmentList = new ArrayList<>();
120+
for (String beanName : fragmentBeanNames) {
121+
122+
BeanDefinition fragmentBeanDefinition = beanFactory.getBeanDefinition(beanName);
123+
ValueHolder argumentValue = fragmentBeanDefinition.getConstructorArgumentValues().getArgumentValue(0,
124+
String.class);
125+
ValueHolder argumentValue1 = fragmentBeanDefinition.getConstructorArgumentValues().getArgumentValue(1, null, null,
126+
null);
127+
Object fragmentClassName = argumentValue.getValue();
128+
129+
try {
130+
Class<?> type = ClassUtils.forName(fragmentClassName.toString(), beanFactory.getBeanClassLoader());
131+
132+
if (argumentValue1 != null && argumentValue1.getValue() instanceof RuntimeBeanReference rbf) {
133+
BeanDefinition implBeanDef = beanFactory.getBeanDefinition(rbf.getBeanName());
134+
Class implClass = ClassUtils.forName(implBeanDef.getBeanClassName(), beanFactory.getBeanClassLoader());
135+
fragmentList.add(new RepositoryFragment.ImplementedRepositoryFragment(type, implClass));
136+
} else {
137+
fragmentList.add(RepositoryFragment.structural(type));
138+
}
139+
} catch (ClassNotFoundException e) {
140+
throw new RuntimeException(e);
141+
}
142+
}
143+
return fragmentList;
78144
}
79145

80-
private static Supplier<org.springframework.data.repository.core.RepositoryMetadata> metadataSupplier(
81-
RepositoryConfiguration<?> metadata, ConfigurableListableBeanFactory beanFactory) {
82-
return Lazy.of(() -> new DefaultRepositoryMetadata(forName(metadata.getRepositoryInterface(), beanFactory)));
83-
}
146+
static abstract class Dummy implements CrudRepository<Object, Object>, PagingAndSortingRepository<Object, Object> {}
84147

85148
static Class<?> forName(String name, ConfigurableListableBeanFactory beanFactory) {
86149
try {

src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotContribution.java

+8-13
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import java.util.function.Predicate;
2929

3030
import org.jspecify.annotations.Nullable;
31-
3231
import org.springframework.aop.SpringProxy;
3332
import org.springframework.aop.framework.Advised;
3433
import org.springframework.aot.generate.GenerationContext;
@@ -49,7 +48,6 @@
4948
import org.springframework.data.repository.Repository;
5049
import org.springframework.data.repository.aot.generate.RepositoryContributor;
5150
import org.springframework.data.repository.core.RepositoryInformation;
52-
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
5351
import org.springframework.data.repository.core.support.RepositoryFragment;
5452
import org.springframework.data.util.Predicates;
5553
import org.springframework.data.util.QTypeContributor;
@@ -90,8 +88,7 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
9088
* @throws IllegalArgumentException if the {@link RepositoryRegistrationAotProcessor} is {@literal null}.
9189
* @see RepositoryRegistrationAotProcessor
9290
*/
93-
protected RepositoryRegistrationAotContribution(
94-
RepositoryRegistrationAotProcessor processor) {
91+
protected RepositoryRegistrationAotContribution(RepositoryRegistrationAotProcessor processor) {
9592

9693
Assert.notNull(processor, "RepositoryRegistrationAotProcessor must not be null");
9794

@@ -108,8 +105,7 @@ protected RepositoryRegistrationAotContribution(
108105
* @throws IllegalArgumentException if the {@link RepositoryRegistrationAotProcessor} is {@literal null}.
109106
* @see RepositoryRegistrationAotProcessor
110107
*/
111-
public static RepositoryRegistrationAotContribution fromProcessor(
112-
RepositoryRegistrationAotProcessor processor) {
108+
public static RepositoryRegistrationAotContribution fromProcessor(RepositoryRegistrationAotProcessor processor) {
113109
return new RepositoryRegistrationAotContribution(processor);
114110
}
115111

@@ -255,7 +251,8 @@ private void contributeRepositoryInfo(AotRepositoryContext repositoryContext, Ge
255251
});
256252

257253
implementation.ifPresent(impl -> {
258-
contribution.getRuntimeHints().reflection().registerType(impl.getClass(), hint -> {
254+
Class<?> typeToRegister = impl instanceof Class c ? c : impl.getClass();
255+
contribution.getRuntimeHints().reflection().registerType(typeToRegister, hint -> {
259256

260257
hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
261258

@@ -365,18 +362,16 @@ public Predicate<Class<?>> typeFilter() { // like only document ones. // TODO: A
365362

366363
@SuppressWarnings("rawtypes")
367364
private DefaultAotRepositoryContext buildAotRepositoryContext(RegisteredBean bean,
368-
RepositoryConfiguration<?> repositoryMetadata) {
365+
RepositoryConfiguration<?> repositoryConfiguration) {
369366

370367
DefaultAotRepositoryContext repositoryContext = new DefaultAotRepositoryContext(
371368
AotContext.from(getBeanFactory(), getRepositoryRegistrationAotProcessor().getEnvironment()));
372369

373-
RepositoryFactoryBeanSupport rfbs = bean.getBeanFactory().getBean("&" + bean.getBeanName(),
374-
RepositoryFactoryBeanSupport.class);
375-
376370
repositoryContext.setBeanName(bean.getBeanName());
377-
repositoryContext.setBasePackages(repositoryMetadata.getBasePackages().toSet());
371+
repositoryContext.setBasePackages(repositoryConfiguration.getBasePackages().toSet());
378372
repositoryContext.setIdentifyingAnnotations(resolveIdentifyingAnnotations());
379-
repositoryContext.setRepositoryInformation(rfbs.getRepositoryInformation());
373+
repositoryContext
374+
.setRepositoryInformation(RepositoryBeanDefinitionReader.repositoryInformation(repositoryConfiguration, bean));
380375

381376
return repositoryContext;
382377
}

src/main/java/org/springframework/data/repository/core/RepositoryInformation.java

+5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.lang.reflect.Method;
1919
import java.util.List;
2020

21+
import org.jspecify.annotations.Nullable;
2122
import org.springframework.data.repository.core.support.RepositoryComposition;
2223

2324
/**
@@ -105,4 +106,8 @@ default boolean hasQueryMethods() {
105106
*/
106107
RepositoryComposition getRepositoryComposition();
107108

109+
default @Nullable String moduleName() {
110+
return null;
111+
}
112+
108113
}

src/main/java/org/springframework/data/repository/core/RepositoryInformationSupport.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ protected boolean isQueryMethodCandidate(Method method) {
184184
return true;
185185
}
186186

187-
private RepositoryMetadata getMetadata() {
187+
protected RepositoryMetadata getMetadata() {
188188
return metadata.get();
189189
}
190190

0 commit comments

Comments
 (0)