Skip to content

Commit 29231f9

Browse files
Introduce ManagedTypes & TypeScanner.
1 parent a51b176 commit 29231f9

14 files changed

+822
-45
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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.domain;
17+
18+
import java.util.ArrayList;
19+
import java.util.Collections;
20+
import java.util.List;
21+
import java.util.function.Consumer;
22+
import java.util.function.Supplier;
23+
import java.util.stream.Stream;
24+
25+
import org.springframework.data.util.Lazy;
26+
27+
/**
28+
* Types managed by a Spring Data implementation. Used to predefine a set of know entities that might need processing
29+
* during the Spring container, Spring Data Repository initialization phase.
30+
*
31+
* @author Christoph Strobl
32+
* @author John Blum
33+
* @see java.lang.FunctionalInterface
34+
* @since 3.0
35+
*/
36+
@FunctionalInterface
37+
public interface ManagedTypes {
38+
39+
/**
40+
* Factory method used to construct a new instance of {@link ManagedTypes} containing no {@link Class types}.
41+
*
42+
* @return an empty {@link ManagedTypes} instance.
43+
* @see java.util.Collections#emptySet()
44+
* @see #of(Iterable)
45+
*/
46+
static ManagedTypes empty() {
47+
return of(Collections.emptySet());
48+
}
49+
50+
/**
51+
* Factory method used to construct {@link ManagedTypes} from the given, required {@link Iterable} of {@link Class
52+
* types}.
53+
*
54+
* @param types {@link Iterable} of {@link Class types} used to initialize the {@link ManagedTypes}; must not be
55+
* {@literal null}.
56+
* @return new instance of {@link ManagedTypes} initialized the given, required {@link Iterable} of {@link Class
57+
* types}.
58+
* @see java.lang.Iterable
59+
* @see #of(Stream)
60+
* @see #of(Supplier)
61+
*/
62+
static ManagedTypes of(Iterable<? extends Class<?>> types) {
63+
return types::forEach;
64+
}
65+
66+
/**
67+
* Factory method used to construct {@link ManagedTypes} from the given, required {@link Stream} of {@link Class
68+
* types}.
69+
*
70+
* @param types {@link Stream} of {@link Class types} used to initialize the {@link ManagedTypes}; must not be
71+
* {@literal null}.
72+
* @return new instance of {@link ManagedTypes} initialized the given, required {@link Stream} of {@link Class types}.
73+
* @see java.util.stream.Stream
74+
* @see #of(Iterable)
75+
* @see #of(Supplier)
76+
*/
77+
static ManagedTypes of(Stream<? extends Class<?>> types) {
78+
return types::forEach;
79+
}
80+
81+
/**
82+
* Factory method used to construct {@link ManagedTypes} from the given, required {@link Supplier} of an
83+
* {@link Iterable} of {@link Class types}.
84+
*
85+
* @param dataProvider {@link Supplier} of an {@link Iterable} of {@link Class types} used to lazily initialize the
86+
* {@link ManagedTypes}; must not be {@literal null}.
87+
* @return new instance of {@link ManagedTypes} initialized the given, required {@link Supplier} of an
88+
* {@link Iterable} of {@link Class types}.
89+
* @see java.util.function.Supplier
90+
* @see java.lang.Iterable
91+
* @see #of(Iterable)
92+
* @see #of(Stream)
93+
*/
94+
static ManagedTypes of(Supplier<Iterable<Class<?>>> dataProvider) {
95+
96+
return new ManagedTypes() {
97+
98+
final Lazy<Iterable<Class<?>>> lazyProvider = Lazy.of(dataProvider);
99+
100+
@Override
101+
public void forEach(Consumer<Class<?>> action) {
102+
lazyProvider.get().forEach(action);
103+
}
104+
};
105+
}
106+
107+
/**
108+
* Applies the given {@link Consumer action} to each of the {@link Class types} contained in this {@link ManagedTypes}
109+
* instance.
110+
*
111+
* @param action {@link Consumer} defining the action to perform on the {@link Class types} contained in this
112+
* {@link ManagedTypes} instance; must not be {@literal null}.
113+
* @see java.util.function.Consumer
114+
*/
115+
void forEach(Consumer<Class<?>> action);
116+
117+
/**
118+
* Returns all the {@link ManagedTypes} in a {@link List}.
119+
*
120+
* @return these {@link ManagedTypes} in a {@link List}; never {@literal null}.
121+
* @see java.util.List
122+
*/
123+
default List<Class<?>> toList() {
124+
125+
List<Class<?>> list = new ArrayList<>(100);
126+
forEach(list::add);
127+
return list;
128+
}
129+
}

src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java

+15-3
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.springframework.context.ApplicationEventPublisherAware;
4343
import org.springframework.core.KotlinDetector;
4444
import org.springframework.core.NativeDetector;
45+
import org.springframework.data.domain.ManagedTypes;
4546
import org.springframework.data.mapping.MappingException;
4647
import org.springframework.data.mapping.PersistentEntity;
4748
import org.springframework.data.mapping.PersistentProperty;
@@ -101,7 +102,8 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
101102
private @Nullable ApplicationEventPublisher applicationEventPublisher;
102103
private EvaluationContextProvider evaluationContextProvider = EvaluationContextProvider.DEFAULT;
103104

104-
private Set<? extends Class<?>> initialEntitySet = new HashSet<>();
105+
private ManagedTypes managedTypes = ManagedTypes.empty();
106+
105107
private boolean strict = false;
106108
private SimpleTypeHolder simpleTypeHolder = SimpleTypeHolder.DEFAULT;
107109

@@ -143,7 +145,17 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
143145
* @param initialEntitySet
144146
*/
145147
public void setInitialEntitySet(Set<? extends Class<?>> initialEntitySet) {
146-
this.initialEntitySet = initialEntitySet;
148+
setManagedTypes(ManagedTypes.of(initialEntitySet));
149+
}
150+
151+
/**
152+
* Sets the types to populate the context initially.
153+
*
154+
* @param managedTypes must not be {@literal null}. Use {@link ManagedTypes#empty()} instead;
155+
* @since 3.0
156+
*/
157+
public void setManagedTypes(ManagedTypes managedTypes) {
158+
this.managedTypes = managedTypes;
147159
}
148160

149161
/**
@@ -467,7 +479,7 @@ public void afterPropertiesSet() {
467479
* context.
468480
*/
469481
public void initialize() {
470-
initialEntitySet.forEach(this::addPersistentEntity);
482+
managedTypes.forEach(this::addPersistentEntity);
471483
}
472484

473485
/**

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

+73-1
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@
2121
import java.util.HashMap;
2222
import java.util.List;
2323
import java.util.Map;
24+
import java.util.Set;
25+
import java.util.function.Consumer;
26+
import java.util.function.Supplier;
2427
import java.util.stream.Collectors;
2528

2629
import org.apache.commons.logging.Log;
2730
import org.apache.commons.logging.LogFactory;
28-
2931
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
3032
import org.springframework.beans.factory.config.DependencyDescriptor;
3133
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
@@ -44,10 +46,14 @@
4446
import org.springframework.core.log.LogMessage;
4547
import org.springframework.core.metrics.ApplicationStartup;
4648
import org.springframework.core.metrics.StartupStep;
49+
import org.springframework.data.domain.ManagedTypes;
4750
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
51+
import org.springframework.data.util.Lazy;
52+
import org.springframework.data.util.TypeScanner;
4853
import org.springframework.lang.Nullable;
4954
import org.springframework.util.Assert;
5055
import org.springframework.util.StopWatch;
56+
import org.springframework.util.StringUtils;
5157

5258
/**
5359
* Delegate for configuration integration to reuse the general way of detecting repositories. Customization is done by
@@ -203,9 +209,49 @@ public List<BeanComponentDefinition> registerRepositoriesIn(BeanDefinitionRegist
203209
watch.getLastTaskTimeMillis(), configurations.size(), extension.getModuleName()));
204210
}
205211

212+
registerManagedTypes(registry, extension,
213+
() -> configurations.stream().flatMap(it -> it.getBasePackages().stream()).collect(Collectors.toSet()));
206214
return definitions;
207215
}
208216

217+
protected void registerManagedTypes(BeanDefinitionRegistry registry, RepositoryConfigurationExtension extension,
218+
Supplier<Set<String>> basePackages) {
219+
220+
String beanName = String.format("%s.managed-types", extension.getModulePrefix());
221+
if (!registry.isBeanNameInUse(beanName)) {
222+
223+
// this needs to be lazy or we'd resolve types to early maybe
224+
Supplier<Set<Class<?>>> args = new Supplier<Set<Class<?>>>() {
225+
226+
@Override
227+
public Set<Class<?>> get() {
228+
229+
Set<String> packagesToScan = basePackages.get();
230+
231+
ApplicationStartup startup = getStartup(registry);
232+
StartupStep typeScan = startup.start("spring.data.type.scanning");
233+
typeScan.tag("dataModule", extension.getModuleName());
234+
typeScan.tag("basePackages", () -> StringUtils.collectionToCommaDelimitedString(packagesToScan));
235+
236+
Set<Class<?>> types = TypeScanner.typeScanner(resourceLoader) //
237+
.scanPackages(packagesToScan) //
238+
.forTypesAnnotatedWith(extension.getIdentifyingAnnotations()) //
239+
.collectAsSet();
240+
241+
//TODO: should the set include the repo domain types
242+
243+
typeScan.tag("type.count", Integer.toString(types.size()));
244+
typeScan.end();
245+
246+
return types;
247+
}
248+
};
249+
250+
registry.registerBeanDefinition(beanName, BeanDefinitionBuilder.rootBeanDefinition(ManagedTypesBean.class)
251+
.addConstructorArgValue(args).getBeanDefinition());
252+
}
253+
}
254+
209255
/**
210256
* Registers a {@link LazyRepositoryInjectionPointResolver} over the default
211257
* {@link ContextAnnotationAutowireCandidateResolver} to make injection points of lazy repositories lazy, too. Will
@@ -332,4 +378,30 @@ protected boolean isLazy(DependencyDescriptor descriptor) {
332378
return lazyInit;
333379
}
334380
}
381+
382+
/**
383+
* Lazy evalutating ManagedTypes implementation using a supplier to avoid eager class instantiation. This type is
384+
* intended to be rewritten during AOT processing.
385+
*
386+
* @author Christoph Strobl
387+
* @since 3.0
388+
*/
389+
static class ManagedTypesBean implements ManagedTypes {
390+
391+
private Lazy<Set<Class<?>>> types;
392+
393+
ManagedTypesBean(Supplier<Set<Class<?>>> types) {
394+
this.types = Lazy.of(types);
395+
}
396+
397+
@Override
398+
public List<Class<?>> toList() {
399+
return new ArrayList<>(types.get());
400+
}
401+
402+
@Override
403+
public void forEach(Consumer<Class<?>> action) {
404+
types.get().forEach(action);
405+
}
406+
}
335407
}

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

+26-1
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,22 @@
1515
*/
1616
package org.springframework.data.repository.config;
1717

18+
import java.lang.annotation.Annotation;
1819
import java.util.Collection;
20+
import java.util.Collections;
1921

2022
import org.springframework.beans.factory.config.BeanDefinition;
2123
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
2224
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
2325
import org.springframework.core.io.ResourceLoader;
26+
import org.springframework.util.StringUtils;
2427

2528
/**
2629
* SPI to implement store specific extension to the repository bean definition registration process.
2730
*
2831
* @see RepositoryConfigurationExtensionSupport
2932
* @author Oliver Gierke
33+
* @author Christoph Strobl
3034
*/
3135
public interface RepositoryConfigurationExtension {
3236

@@ -35,7 +39,17 @@ public interface RepositoryConfigurationExtension {
3539
*
3640
* @return
3741
*/
38-
String getModuleName();
42+
default String getModuleName() {
43+
return StringUtils.capitalize(getModulePrefix());
44+
}
45+
46+
/**
47+
* Returns the prefix of the module to be used to create the default location for Spring Data named queries and module
48+
* specific bean definitions.
49+
*
50+
* @return must not be {@literal null}.
51+
*/
52+
String getModulePrefix();
3953

4054
/**
4155
* Returns all {@link RepositoryConfiguration}s obtained through the given {@link RepositoryConfigurationSource}.
@@ -65,6 +79,17 @@ <T extends RepositoryConfigurationSource> Collection<RepositoryConfiguration<T>>
6579
*/
6680
String getRepositoryFactoryBeanClassName();
6781

82+
/**
83+
* Return the annotations to scan domain types for when evaluating repository interfaces for store assignment. Modules
84+
* should return the annotations that identify a domain type as managed by the store explicitly.
85+
*
86+
* @return
87+
* @since 3.0
88+
*/
89+
default Collection<Class<? extends Annotation>> getIdentifyingAnnotations() {
90+
return Collections.emptySet();
91+
}
92+
6893
/**
6994
* Callback to register additional bean definitions for a {@literal repositories} root node. This usually includes
7095
* beans you have to set up once independently of the number of repositories to be created. Will be called before any

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

-23
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,6 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit
5959

6060
private boolean noMultiStoreSupport = false;
6161

62-
@Override
63-
public String getModuleName() {
64-
return StringUtils.capitalize(getModulePrefix());
65-
}
66-
6762
public <T extends RepositoryConfigurationSource> Collection<RepositoryConfiguration<T>> getRepositoryConfigurations(
6863
T configSource, ResourceLoader loader) {
6964
return getRepositoryConfigurations(configSource, loader, false);
@@ -108,30 +103,12 @@ public String getDefaultNamedQueryLocation() {
108103
public void registerBeansForRoot(BeanDefinitionRegistry registry,
109104
RepositoryConfigurationSource configurationSource) {}
110105

111-
/**
112-
* Returns the prefix of the module to be used to create the default location for Spring Data named queries.
113-
*
114-
* @return must not be {@literal null}.
115-
*/
116-
protected abstract String getModulePrefix();
117-
118106
public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSource source) {}
119107

120108
public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource config) {}
121109

122110
public void postProcess(BeanDefinitionBuilder builder, XmlRepositoryConfigurationSource config) {}
123111

124-
/**
125-
* Return the annotations to scan domain types for when evaluating repository interfaces for store assignment. Modules
126-
* should return the annotations that identify a domain type as managed by the store explicitly.
127-
*
128-
* @return
129-
* @since 1.9
130-
*/
131-
protected Collection<Class<? extends Annotation>> getIdentifyingAnnotations() {
132-
return Collections.emptySet();
133-
}
134-
135112
/**
136113
* Returns the types that indicate a store match when inspecting repositories for strict matches.
137114
*

0 commit comments

Comments
 (0)