Skip to content

Commit 37b7f48

Browse files
committed
Make dependency tests based on ArchUnit.
The DependencyTests are reimplemented using ArchUnit and enabled. This uncovered some dependency circles which got fixed: AOT related code was abstracted so configuration code doesn't have to know about AOT specific configuration which is injected using spring.factories. AOT configuration for Querydsl was moved to a separate class as part of the querydsl package. The tests currently fail because `aot` causes cycles. Original pull request #2706
1 parent e50057c commit 37b7f48

11 files changed

+332
-88
lines changed

pom.xml

+8-7
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
<scala>2.11.7</scala>
3434
<xmlbeam>1.4.24</xmlbeam>
35+
<archunit.version>1.0.0</archunit.version>
3536
<java-module-name>spring.data.commons</java-module-name>
3637

3738
</properties>
@@ -334,20 +335,20 @@
334335
<optional>true</optional>
335336
</dependency>
336337

337-
<dependency>
338-
<groupId>de.schauderhaft.degraph</groupId>
339-
<artifactId>degraph-check</artifactId>
340-
<version>0.1.4</version>
341-
<scope>test</scope>
342-
</dependency>
343-
344338
<dependency>
345339
<groupId>org.jmolecules.integrations</groupId>
346340
<artifactId>jmolecules-spring</artifactId>
347341
<version>${jmolecules-integration}</version>
348342
<optional>true</optional>
349343
</dependency>
350344

345+
<dependency>
346+
<groupId>com.tngtech.archunit</groupId>
347+
<artifactId>archunit</artifactId>
348+
<version>${archunit.version}</version>
349+
<scope>test</scope>
350+
</dependency>
351+
351352
</dependencies>
352353

353354
<build>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.querydsl;
17+
18+
import com.querydsl.core.types.Predicate;
19+
import org.springframework.aot.hint.MemberCategory;
20+
import org.springframework.aot.hint.RuntimeHints;
21+
import org.springframework.aot.hint.RuntimeHintsRegistrar;
22+
import org.springframework.aot.hint.TypeReference;
23+
import org.springframework.beans.factory.BeanFactory;
24+
import org.springframework.core.io.InputStreamSource;
25+
import org.springframework.data.domain.Example;
26+
import org.springframework.data.mapping.context.MappingContext;
27+
import org.springframework.data.repository.core.RepositoryMetadata;
28+
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
29+
import org.springframework.data.repository.core.support.RepositoryFragment;
30+
import org.springframework.data.repository.core.support.RepositoryFragmentsFactoryBean;
31+
import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport;
32+
import org.springframework.data.repository.query.FluentQuery;
33+
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
34+
import org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery;
35+
import org.springframework.data.repository.query.QueryByExampleExecutor;
36+
import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor;
37+
import org.springframework.lang.Nullable;
38+
import org.springframework.util.ClassUtils;
39+
40+
import java.util.Arrays;
41+
import java.util.Properties;
42+
43+
/**
44+
* {@link RuntimeHintsRegistrar} holding required hints to bootstrap Querydsl repositories. <br />
45+
* Already registered via {@literal aot.factories}.
46+
*
47+
* @author Christoph Strobl
48+
* @author Mark Paluch
49+
* @since 3.0
50+
*/
51+
class QuerydslRepositoryRuntimeHints implements RuntimeHintsRegistrar {
52+
53+
private static final boolean PROJECT_REACTOR_PRESENT = ClassUtils.isPresent("reactor.core.publisher.Flux",
54+
QuerydslRepositoryRuntimeHints.class.getClassLoader());
55+
56+
@Override
57+
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
58+
59+
if (QuerydslUtils.QUERY_DSL_PRESENT) {
60+
61+
// repository infrastructure
62+
hints.reflection().registerTypes(Arrays.asList( //
63+
TypeReference.of(Predicate.class), //
64+
TypeReference.of(QuerydslPredicateExecutor.class)), builder -> {
65+
builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
66+
});
67+
68+
if (PROJECT_REACTOR_PRESENT) {
69+
// repository infrastructure
70+
hints.reflection().registerTypes(Arrays.asList( //
71+
TypeReference.of(ReactiveQuerydslPredicateExecutor.class)), builder -> {
72+
builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
73+
});
74+
}
75+
}
76+
}
77+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.springframework.data.repository.aot;
2+
3+
import org.springframework.data.repository.config.RepositoryConfigurationPostProcessor;
4+
5+
class FallbackRepositoryAotConfigurationPostProcessor extends RepositoryRegistrationAotProcessor implements RepositoryConfigurationPostProcessor.FallbackRepositoryConfigurationPostProcessor {
6+
}

src/main/java/org/springframework/data/repository/aot/RepositoryRegistrationAotProcessor.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.springframework.data.aot.TypeContributor;
3939
import org.springframework.data.repository.config.RepositoryConfiguration;
4040
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
41+
import org.springframework.data.repository.config.RepositoryConfigurationPostProcessor;
4142
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
4243
import org.springframework.lang.Nullable;
4344
import org.springframework.util.Assert;
@@ -56,16 +57,14 @@
5657
* provide custom logic for contributing additional (eg. reflection) configuration. By default, reflection configuration
5758
* will be added for types reachable from the repository declaration and query methods as well as all used
5859
* {@link Annotation annotations} from the {@literal org.springframework.data} namespace.
59-
* </p>
60-
* The processor is typically configured via {@link RepositoryConfigurationExtension#getRepositoryAotProcessor()} and
61-
* gets added by the {@link org.springframework.data.repository.config.RepositoryConfigurationDelegate}.
60+
6261
*
6362
* @author Christoph Strobl
6463
* @author John Blum
6564
* @since 3.0
6665
*/
6766
@SuppressWarnings("unused")
68-
public class RepositoryRegistrationAotProcessor implements BeanRegistrationAotProcessor, BeanFactoryAware {
67+
public class RepositoryRegistrationAotProcessor implements BeanRegistrationAotProcessor, BeanFactoryAware, RepositoryConfigurationPostProcessor {
6968

7069
private ConfigurableListableBeanFactory beanFactory;
7170

@@ -117,6 +116,7 @@ protected ConfigurableListableBeanFactory getBeanFactory() {
117116
return this.beanFactory;
118117
}
119118

119+
@Override
120120
public void setConfigMap(@Nullable Map<String, RepositoryConfiguration<?>> configMap) {
121121
this.configMap = configMap;
122122
}

src/main/java/org/springframework/data/repository/aot/hint/RepositoryRuntimeHints.java

-18
Original file line numberDiff line numberDiff line change
@@ -87,24 +87,6 @@ public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader)
8787
});
8888
}
8989

90-
if (QuerydslUtils.QUERY_DSL_PRESENT) {
91-
92-
// repository infrastructure
93-
hints.reflection().registerTypes(Arrays.asList( //
94-
TypeReference.of(Predicate.class), //
95-
TypeReference.of(QuerydslPredicateExecutor.class)), builder -> {
96-
builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
97-
});
98-
99-
if (PROJECT_REACTOR_PRESENT) {
100-
// repository infrastructure
101-
hints.reflection().registerTypes(Arrays.asList( //
102-
TypeReference.of(ReactiveQuerydslPredicateExecutor.class)), builder -> {
103-
builder.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS);
104-
});
105-
}
106-
}
107-
10890
// named queries
10991
hints.reflection().registerTypes(Arrays.asList( //
11092
TypeReference.of(Properties.class), //

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

+50-26
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.springframework.core.log.LogMessage;
4747
import org.springframework.core.metrics.ApplicationStartup;
4848
import org.springframework.core.metrics.StartupStep;
49+
import org.springframework.data.repository.config.RepositoryConfigurationPostProcessor.FallbackRepositoryConfigurationPostProcessor;
4950
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
5051
import org.springframework.lang.Nullable;
5152
import org.springframework.util.Assert;
@@ -83,11 +84,11 @@ public class RepositoryConfigurationDelegate {
8384
* {@link ResourceLoader} and {@link Environment}.
8485
*
8586
* @param configurationSource must not be {@literal null}.
86-
* @param resourceLoader must not be {@literal null}.
87-
* @param environment must not be {@literal null}.
87+
* @param resourceLoader must not be {@literal null}.
88+
* @param environment must not be {@literal null}.
8889
*/
8990
public RepositoryConfigurationDelegate(RepositoryConfigurationSource configurationSource,
90-
ResourceLoader resourceLoader, Environment environment) {
91+
ResourceLoader resourceLoader, Environment environment) {
9192

9293
this.isXml = configurationSource instanceof XmlRepositoryConfigurationSource;
9394
boolean isAnnotation = configurationSource instanceof AnnotationRepositoryConfigurationSource;
@@ -106,13 +107,13 @@ public RepositoryConfigurationDelegate(RepositoryConfigurationSource configurati
106107
* Defaults the environment in case the given one is null. Used as fallback, in case the legacy constructor was
107108
* invoked.
108109
*
109-
* @param environment can be {@literal null}.
110+
* @param environment can be {@literal null}.
110111
* @param resourceLoader can be {@literal null}.
111112
* @return the given {@link Environment} if not {@literal null}, a configured {@link Environment}, or a default
112-
* {@link Environment}.
113+
* {@link Environment}.
113114
*/
114115
private static Environment defaultEnvironment(@Nullable Environment environment,
115-
@Nullable ResourceLoader resourceLoader) {
116+
@Nullable ResourceLoader resourceLoader) {
116117

117118
if (environment != null) {
118119
return environment;
@@ -125,14 +126,14 @@ private static Environment defaultEnvironment(@Nullable Environment environment,
125126
/**
126127
* Registers the discovered repositories in the given {@link BeanDefinitionRegistry}.
127128
*
128-
* @param registry {@link BeanDefinitionRegistry} in which to register the repository bean.
129+
* @param registry {@link BeanDefinitionRegistry} in which to register the repository bean.
129130
* @param extension {@link RepositoryConfigurationExtension} for the module.
130131
* @return {@link BeanComponentDefinition}s for all repository bean definitions found.
131132
* @see org.springframework.data.repository.config.RepositoryConfigurationExtension
132133
* @see org.springframework.beans.factory.support.BeanDefinitionRegistry
133134
*/
134135
public List<BeanComponentDefinition> registerRepositoriesIn(BeanDefinitionRegistry registry,
135-
RepositoryConfigurationExtension extension) {
136+
RepositoryConfigurationExtension extension) {
136137

137138
if (logger.isInfoEnabled()) {
138139
logger.info(LogMessage.format("Bootstrapping Spring Data %s repositories in %s mode.", //
@@ -210,29 +211,52 @@ public List<BeanComponentDefinition> registerRepositoriesIn(BeanDefinitionRegist
210211
watch.getLastTaskTimeMillis(), configurations.size(), extension.getModuleName()));
211212
}
212213

213-
// TODO: AOT Processing -> guard this one with a flag so it's not always present
214-
// TODO: With regard to AOT Processing, perhaps we need to be smart and detect whether "core" AOT components are
215-
// (or rather configuration is) present on the classpath to enable Spring Data AOT component registration.
216-
registerAotComponents(registry, extension, metadataByRepositoryBeanName);
214+
registerPostProcessorComponents(registry, extension, metadataByRepositoryBeanName);
217215

218216
return definitions;
219217
}
220218

221-
private void registerAotComponents(BeanDefinitionRegistry registry, RepositoryConfigurationExtension extension,
222-
Map<String, RepositoryConfigurationAdapter<?>> metadataByRepositoryBeanName) {
219+
private void registerPostProcessorComponents(BeanDefinitionRegistry registry, RepositoryConfigurationExtension extension,
220+
Map<String, RepositoryConfigurationAdapter<?>> metadataByRepositoryBeanName) {
223221

224-
// module-specific repository aot processor
225-
String repositoryAotProcessorBeanName = String.format("data-%s.repository-aot-processor" /* might be duplicate */,
226-
extension.getModuleIdentifier());
222+
boolean hasPostProcessors = false;
223+
List<RepositoryConfigurationPostProcessor> processors = SpringFactoriesLoader.loadFactories(RepositoryConfigurationPostProcessor.class, resourceLoader.getClassLoader());
224+
for (RepositoryConfigurationPostProcessor processor : processors) {
227225

228-
if (!registry.isBeanNameInUse(repositoryAotProcessorBeanName)) {
226+
if (!processor.supports(extension)) {
227+
continue;
228+
}
229+
230+
hasPostProcessors = true;
231+
registerRepositoryPostProcessor(registry, extension, processor.getClass().getName(), metadataByRepositoryBeanName);
232+
}
233+
234+
if (!hasPostProcessors) {
229235

230-
BeanDefinitionBuilder repositoryAotProcessor = BeanDefinitionBuilder
231-
.rootBeanDefinition(extension.getRepositoryAotProcessor()).setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
236+
List<FallbackRepositoryConfigurationPostProcessor> fallback = SpringFactoriesLoader.loadFactories(FallbackRepositoryConfigurationPostProcessor.class, resourceLoader.getClassLoader());
232237

233-
repositoryAotProcessor.addPropertyValue("configMap", metadataByRepositoryBeanName);
238+
for (RepositoryConfigurationPostProcessor processor : fallback) {
234239

235-
registry.registerBeanDefinition(repositoryAotProcessorBeanName, repositoryAotProcessor.getBeanDefinition());
240+
if (!processor.supports(extension)) {
241+
continue;
242+
}
243+
244+
registerRepositoryPostProcessor(registry, extension, processor.getClass().getName(), metadataByRepositoryBeanName);
245+
}
246+
}
247+
}
248+
249+
private static void registerRepositoryPostProcessor(BeanDefinitionRegistry registry, RepositoryConfigurationExtension extension, String className, Map<String, RepositoryConfigurationAdapter<?>> metadataByRepositoryBeanName) {
250+
251+
String repositoryProcessorBeanName = String.format("data-%s.repository-post-processor.%s",
252+
extension.getModuleIdentifier(), className);
253+
254+
if (!registry.isBeanNameInUse(repositoryProcessorBeanName)) {
255+
BeanDefinitionBuilder repositoryPostProcessor = BeanDefinitionBuilder
256+
.rootBeanDefinition(className).setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
257+
258+
repositoryPostProcessor.addPropertyValue("configMap", metadataByRepositoryBeanName);
259+
registry.registerBeanDefinition(repositoryProcessorBeanName, repositoryPostProcessor.getBeanDefinition());
236260
}
237261
}
238262

@@ -242,10 +266,10 @@ private void registerAotComponents(BeanDefinitionRegistry registry, RepositoryCo
242266
* augment the {@link LazyRepositoryInjectionPointResolver}'s configuration if there already is one configured.
243267
*
244268
* @param configurations must not be {@literal null}.
245-
* @param registry must not be {@literal null}.
269+
* @param registry must not be {@literal null}.
246270
*/
247271
private static void potentiallyLazifyRepositories(Map<String, RepositoryConfiguration<?>> configurations,
248-
BeanDefinitionRegistry registry, BootstrapMode mode) {
272+
BeanDefinitionRegistry registry, BootstrapMode mode) {
249273

250274
if (!DefaultListableBeanFactory.class.isInstance(registry) || BootstrapMode.DEFAULT.equals(mode)) {
251275
return;
@@ -283,7 +307,7 @@ private static void potentiallyLazifyRepositories(Map<String, RepositoryConfigur
283307
* scanning.
284308
*
285309
* @return {@literal true} if multiple data store repository implementations are present in the application. This
286-
* typically means an Spring application is using more than 1 type of data store.
310+
* typically means an Spring application is using more than 1 type of data store.
287311
*/
288312
private boolean multipleStoresDetected() {
289313

@@ -333,7 +357,7 @@ public LazyRepositoryInjectionPointResolver(Map<String, RepositoryConfiguration<
333357
*
334358
* @param configurations must not be {@literal null}.
335359
* @return a new {@link LazyRepositoryInjectionPointResolver} that will have its configurations augmented with the
336-
* given ones.
360+
* given ones.
337361
*/
338362
LazyRepositoryInjectionPointResolver withAdditionalConfigurations(
339363
Map<String, RepositoryConfiguration<?>> configurations) {

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

-17
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,10 @@
1818
import java.util.Collection;
1919
import java.util.Locale;
2020

21-
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
2221
import org.springframework.beans.factory.config.BeanDefinition;
2322
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
2423
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
2524
import org.springframework.core.io.ResourceLoader;
26-
import org.springframework.data.repository.aot.RepositoryRegistrationAotProcessor;
27-
import org.springframework.lang.NonNull;
2825

2926
/**
3027
* SPI to implement store specific extension to the repository bean definition registration process.
@@ -54,20 +51,6 @@ default String getModuleIdentifier() {
5451
*/
5552
String getModuleName();
5653

57-
/**
58-
* Returns the {@link BeanRegistrationAotProcessor} type responsible for contributing AOT/native configuration
59-
* required by the Spring Data Repository infrastructure components at native runtime.
60-
*
61-
* @return the {@link BeanRegistrationAotProcessor} type responsible for contributing AOT/native configuration.
62-
* Defaults to {@link RepositoryRegistrationAotProcessor}. Must not be {@literal null}.
63-
* @see org.springframework.beans.factory.aot.BeanRegistrationAotProcessor
64-
* @since 3.0
65-
*/
66-
@NonNull
67-
default Class<? extends BeanRegistrationAotProcessor> getRepositoryAotProcessor() {
68-
return RepositoryRegistrationAotProcessor.class;
69-
}
70-
7154
/**
7255
* Returns all {@link RepositoryConfiguration}s obtained through the given {@link RepositoryConfigurationSource}.
7356
*

0 commit comments

Comments
 (0)