Skip to content

Commit 577117d

Browse files
committed
#2025 - Fix regression in AOT reflection metadata generation.
Fixed the detection of abstract classes in AOT metadata generation so that Jackson mixin types get detected again.
1 parent 3479797 commit 577117d

File tree

5 files changed

+229
-120
lines changed

5 files changed

+229
-120
lines changed

src/main/java/org/springframework/hateoas/aot/AotUtils.java

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
package org.springframework.hateoas.aot;
1717

1818
import java.io.IOException;
19-
import java.util.Arrays;
19+
import java.util.ArrayList;
20+
import java.util.Collection;
21+
import java.util.Comparator;
2022
import java.util.HashSet;
2123
import java.util.List;
2224
import java.util.Optional;
@@ -28,6 +30,7 @@
2830
import org.springframework.aot.hint.MemberCategory;
2931
import org.springframework.aot.hint.ReflectionHints;
3032
import org.springframework.aot.hint.TypeReference;
33+
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
3134
import org.springframework.beans.factory.config.BeanDefinition;
3235
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
3336
import org.springframework.core.ResolvableType;
@@ -93,6 +96,20 @@ public static void registerTypeForReflection(Class<?> type, ReflectionHints refl
9396
SEEN_TYPES.add(type);
9497
}
9598

99+
public static void registerTypesForReflection(String packageName, ReflectionHints reflection, TypeFilter... filters) {
100+
101+
// Register RepresentationModel types for full reflection
102+
var provider = AotUtils.getScanner(packageName, filters);
103+
104+
LOGGER.info("Registering Spring HATEOAS types in {} for reflection.", packageName);
105+
106+
provider.findClasses()
107+
.sorted(Comparator.comparing(TypeReference::getName))
108+
.peek(type -> LOGGER.debug("> {}", type.getName()))
109+
.forEach(reference -> reflection.registerType(reference, //
110+
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS));
111+
}
112+
96113
/**
97114
* Extracts the generics from the given model type if the given {@link ResolvableType} is assignable.
98115
*
@@ -129,15 +146,28 @@ private static Optional<Class<?>> extractGenerics(Class<?> modelType, Resolvable
129146

130147
public static FullTypeScanner getScanner(String packageName, TypeFilter... includeFilters) {
131148

132-
var provider = new ClassPathScanningCandidateComponentProvider(false);
149+
var provider = new ClassPathScanningCandidateComponentProvider(false) {
150+
151+
@Override
152+
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
153+
return super.isCandidateComponent(beanDefinition) || beanDefinition.getMetadata().isAbstract();
154+
}
155+
};
156+
157+
var filters = new ArrayList<TypeFilter>();
158+
filters.add(new EnforcedPackageFilter(packageName));
159+
filters.add(new AssignableTypeFilter(Object.class));
133160

134161
if (includeFilters.length == 0) {
135-
provider.addIncludeFilter(new AssignableTypeFilter(Object.class));
136-
} else {
137-
Arrays.stream(includeFilters).forEach(provider::addIncludeFilter);
162+
provider.addIncludeFilter(all(filters));
138163
}
139164

140-
provider.addExcludeFilter(new EnforcedPackageFilter(packageName));
165+
for (TypeFilter filter : includeFilters) {
166+
167+
var includeFilterComponents = new ArrayList<>(filters);
168+
includeFilterComponents.add(filter);
169+
provider.addIncludeFilter(all(includeFilterComponents));
170+
}
141171

142172
return () -> provider.findCandidateComponents(packageName).stream()
143173
.map(BeanDefinition::getBeanClassName)
@@ -150,7 +180,7 @@ public static FullTypeScanner getScanner(String packageName, TypeFilter... inclu
150180
*
151181
* @author Oliver Drotbohm
152182
*/
153-
private static class EnforcedPackageFilter implements TypeFilter {
183+
static class EnforcedPackageFilter implements TypeFilter {
154184

155185
private final String referencePackage;
156186

@@ -165,11 +195,30 @@ public EnforcedPackageFilter(String referencePackage) {
165195
@Override
166196
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
167197
throws IOException {
168-
return !referencePackage
198+
return referencePackage
169199
.equals(ClassUtils.getPackageName(metadataReader.getClassMetadata().getClassName()));
170200
}
171201
}
172202

203+
private static TypeFilter all(Collection<TypeFilter> filters) {
204+
205+
return new TypeFilter() {
206+
207+
@Override
208+
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
209+
throws IOException {
210+
211+
for (TypeFilter filter : filters) {
212+
if (!filter.match(metadataReader, metadataReaderFactory)) {
213+
return false;
214+
}
215+
}
216+
217+
return true;
218+
}
219+
};
220+
}
221+
173222
static interface FullTypeScanner {
174223

175224
abstract Stream<TypeReference> findClasses();

src/main/java/org/springframework/hateoas/aot/HateoasTypesRuntimeHints.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
package org.springframework.hateoas.aot;
1717

18-
import org.springframework.aot.hint.MemberCategory;
1918
import org.springframework.aot.hint.RuntimeHints;
2019
import org.springframework.aot.hint.RuntimeHintsRegistrar;
2120
import org.springframework.hateoas.RepresentationModel;
@@ -34,12 +33,9 @@ class HateoasTypesRuntimeHints implements RuntimeHintsRegistrar {
3433
@Override
3534
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
3635

36+
var packageName = RepresentationModel.class.getPackageName();
3737
var reflection = hints.reflection();
3838

39-
AotUtils.getScanner(RepresentationModel.class.getPackageName()) //
40-
.findClasses() //
41-
.forEach(it -> reflection.registerType(it, //
42-
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, //
43-
MemberCategory.INVOKE_DECLARED_METHODS));
39+
AotUtils.registerTypesForReflection(packageName, reflection);
4440
}
4541
}

src/main/java/org/springframework/hateoas/aot/HypermediaTypeAotProcessor.java

Lines changed: 2 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -15,30 +15,18 @@
1515
*/
1616
package org.springframework.hateoas.aot;
1717

18-
import java.io.IOException;
1918
import java.util.Arrays;
20-
import java.util.Comparator;
2119
import java.util.HashSet;
2220
import java.util.List;
2321
import java.util.Set;
24-
import java.util.function.Predicate;
2522
import java.util.stream.Stream;
2623

27-
import org.slf4j.Logger;
28-
import org.slf4j.LoggerFactory;
2924
import org.springframework.aot.generate.GenerationContext;
30-
import org.springframework.aot.hint.MemberCategory;
31-
import org.springframework.aot.hint.TypeReference;
3225
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
3326
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
3427
import org.springframework.beans.factory.aot.BeanRegistrationCode;
3528
import org.springframework.beans.factory.support.RegisteredBean;
3629
import org.springframework.core.annotation.AnnotatedElementUtils;
37-
import org.springframework.core.annotation.MergedAnnotation;
38-
import org.springframework.core.type.classreading.MetadataReader;
39-
import org.springframework.core.type.classreading.MetadataReaderFactory;
40-
import org.springframework.core.type.filter.TypeFilter;
41-
import org.springframework.hateoas.aot.AotUtils.FullTypeScanner;
4230
import org.springframework.hateoas.config.EnableHypermediaSupport;
4331
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
4432
import org.springframework.util.Assert;
@@ -80,8 +68,6 @@ public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registe
8068

8169
static class MediaTypeReflectionAotContribution implements BeanRegistrationAotContribution {
8270

83-
private static final Logger LOGGER = LoggerFactory.getLogger(MediaTypeReflectionAotContribution.class);
84-
8571
private final List<String> mediaTypePackage;
8672
private final Set<String> packagesSeen;
8773

@@ -105,8 +91,6 @@ public MediaTypeReflectionAotContribution(List<String> mediaTypePackage) {
10591
@Override
10692
public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
10793

108-
var reflection = generationContext.getRuntimeHints().reflection();
109-
11094
mediaTypePackage.forEach(it -> {
11195

11296
if (packagesSeen.contains(it)) {
@@ -115,97 +99,9 @@ public void applyTo(GenerationContext generationContext, BeanRegistrationCode be
11599

116100
packagesSeen.add(it);
117101

118-
// Register RepresentationModel types for full reflection
119-
FullTypeScanner provider = AotUtils.getScanner(it, //
120-
new JacksonAnnotationPresentFilter(), //
121-
new JacksonSuperTypeFilter());
122-
123-
LOGGER.info("Registering Spring HATEOAS types in {} for reflection.", it);
124-
125-
provider.findClasses()
126-
.sorted(Comparator.comparing(TypeReference::getName))
127-
.peek(type -> LOGGER.debug("> {}", type.getName()))
128-
.forEach(reference -> reflection.registerType(reference, //
129-
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_METHODS));
102+
new HypermediaTypesRuntimeHints(it) //
103+
.registerHints(generationContext.getRuntimeHints(), getClass().getClassLoader());
130104
});
131105
}
132106
}
133-
134-
static abstract class TraversingTypeFilter implements TypeFilter {
135-
136-
/*
137-
* (non-Javadoc)
138-
* @see org.springframework.core.type.filter.TypeFilter#match(org.springframework.core.type.classreading.MetadataReader, org.springframework.core.type.classreading.MetadataReaderFactory)
139-
*/
140-
@Override
141-
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
142-
throws IOException {
143-
144-
if (doMatch(metadataReader, metadataReaderFactory)) {
145-
return true;
146-
}
147-
148-
var classMetadata = metadataReader.getClassMetadata();
149-
150-
String superClassName = classMetadata.getSuperClassName();
151-
152-
if (superClassName != null && !superClassName.startsWith("java")
153-
&& match(metadataReaderFactory.getMetadataReader(superClassName), metadataReaderFactory)) {
154-
return true;
155-
}
156-
157-
for (String names : classMetadata.getInterfaceNames()) {
158-
159-
MetadataReader reader = metadataReaderFactory.getMetadataReader(names);
160-
161-
if (match(reader, metadataReaderFactory)) {
162-
return true;
163-
}
164-
}
165-
166-
return false;
167-
}
168-
169-
protected abstract boolean doMatch(MetadataReader reader, MetadataReaderFactory factory);
170-
}
171-
172-
static class JacksonAnnotationPresentFilter extends TraversingTypeFilter {
173-
174-
private static final Predicate<String> IS_JACKSON_ANNOTATION = it -> it.startsWith("com.fasterxml.jackson");
175-
176-
/*
177-
* (non-Javadoc)
178-
* @see org.springframework.hateoas.aot.HateoasRuntimeHints.TraversingTypeFilter#doMatch(org.springframework.core.type.classreading.MetadataReader, org.springframework.core.type.classreading.MetadataReaderFactory)
179-
*/
180-
@Override
181-
protected boolean doMatch(MetadataReader reader, MetadataReaderFactory factory) {
182-
183-
var annotationMetadata = reader.getAnnotationMetadata();
184-
185-
// Type annotations
186-
return annotationMetadata
187-
.getAnnotationTypes()
188-
.stream()
189-
.anyMatch(IS_JACKSON_ANNOTATION)
190-
191-
// Method annotations
192-
|| annotationMetadata.getDeclaredMethods().stream()
193-
.flatMap(it -> it.getAnnotations().stream())
194-
.map(MergedAnnotation::getType)
195-
.map(Class::getName)
196-
.anyMatch(IS_JACKSON_ANNOTATION);
197-
}
198-
}
199-
200-
static class JacksonSuperTypeFilter extends TraversingTypeFilter {
201-
202-
/*
203-
* (non-Javadoc)
204-
* @see org.springframework.hateoas.aot.HateoasRuntimeHints.TraversingTypeFilter#doMatch(org.springframework.core.type.classreading.MetadataReader, org.springframework.core.type.classreading.MetadataReaderFactory)
205-
*/
206-
@Override
207-
protected boolean doMatch(MetadataReader reader, MetadataReaderFactory factory) {
208-
return reader.getClassMetadata().getClassName().startsWith("com.fasterxml.jackson");
209-
}
210-
}
211107
}

0 commit comments

Comments
 (0)