Contribute reflection hints for JsonComponent inner classes

Closes gh-33089
This commit is contained in:
Andy Wilkinson 2022-11-09 19:22:26 +00:00
parent b882de7c68
commit 7eb0fe4c89
3 changed files with 88 additions and 4 deletions

View File

@ -17,6 +17,9 @@
package org.springframework.boot.jackson;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
@ -26,6 +29,9 @@ import com.fasterxml.jackson.databind.KeyDeserializer;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
@ -33,6 +39,10 @@ import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.jackson.JsonComponent.Scope;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.MergedAnnotation;
@ -108,7 +118,7 @@ public class JsonComponentModule extends SimpleModule implements BeanFactoryAwar
}
}
private boolean isSuitableInnerClass(Class<?> innerClass) {
private static boolean isSuitableInnerClass(Class<?> innerClass) {
return !Modifier.isAbstract(innerClass.getModifiers()) && (JsonSerializer.class.isAssignableFrom(innerClass)
|| JsonDeserializer.class.isAssignableFrom(innerClass)
|| KeyDeserializer.class.isAssignableFrom(innerClass));
@ -148,4 +158,44 @@ public class JsonComponentModule extends SimpleModule implements BeanFactoryAwar
}
}
static class JsonComponentBeanFactoryInitializationAotProcessor implements BeanFactoryInitializationAotProcessor {
@Override
public BeanFactoryInitializationAotContribution processAheadOfTime(
ConfigurableListableBeanFactory beanFactory) {
String[] jsonComponents = beanFactory.getBeanNamesForAnnotation(JsonComponent.class);
Map<Class<?>, List<Class<?>>> innerComponents = new HashMap<>();
for (String jsonComponent : jsonComponents) {
Class<?> type = beanFactory.getType(jsonComponent, true);
for (Class<?> declaredClass : type.getDeclaredClasses()) {
if (isSuitableInnerClass(declaredClass)) {
innerComponents.computeIfAbsent(type, (t) -> new ArrayList<>()).add(declaredClass);
}
}
}
return innerComponents.isEmpty() ? null : new JsonComponentAotContribution(innerComponents);
}
}
private static class JsonComponentAotContribution implements BeanFactoryInitializationAotContribution {
private final Map<Class<?>, List<Class<?>>> innerComponents;
public JsonComponentAotContribution(Map<Class<?>, List<Class<?>>> innerComponents) {
this.innerComponents = innerComponents;
}
@Override
public void applyTo(GenerationContext generationContext,
BeanFactoryInitializationCode beanFactoryInitializationCode) {
ReflectionHints reflection = generationContext.getRuntimeHints().reflection();
this.innerComponents.forEach((outer, inners) -> {
reflection.registerType(outer, MemberCategory.DECLARED_CLASSES);
inners.forEach((inner) -> reflection.registerType(inner, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
});
}
}
}

View File

@ -14,7 +14,8 @@ org.springframework.boot.web.embedded.undertow.UndertowWebServer.UndertowWebServ
org.springframework.boot.web.server.MimeMappings.MimeMappingsRuntimeHints
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
org.springframework.boot.context.properties.ConfigurationPropertiesBeanFactoryInitializationAotProcessor
org.springframework.boot.context.properties.ConfigurationPropertiesBeanFactoryInitializationAotProcessor,\
org.springframework.boot.jackson.JsonComponentModule.JsonComponentBeanFactoryInitializationAotProcessor
org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
org.springframework.boot.context.properties.ConfigurationPropertiesBeanRegistrationAotProcessor,\

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2022 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.
@ -27,6 +27,16 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.jackson.JsonComponentModule.JsonComponentBeanFactoryInitializationAotProcessor;
import org.springframework.boot.jackson.JsonComponentModuleTests.ComponentWithInnerAbstractClass.AbstractSerializer;
import org.springframework.boot.jackson.JsonComponentModuleTests.ComponentWithInnerAbstractClass.ConcreteSerializer;
import org.springframework.boot.jackson.JsonComponentModuleTests.ComponentWithInnerAbstractClass.NotSuitable;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
@ -112,6 +122,25 @@ class JsonComponentModuleTests {
assertDeserializeForSpecifiedClasses(module);
}
@Test
void aotContributionRegistersReflectionHintsForSuitableInnerClasses() {
load(ComponentWithInnerAbstractClass.class);
ConfigurableListableBeanFactory beanFactory = this.context.getBeanFactory();
BeanFactoryInitializationAotContribution contribution = new JsonComponentBeanFactoryInitializationAotProcessor()
.processAheadOfTime(beanFactory);
TestGenerationContext generationContext = new TestGenerationContext();
contribution.applyTo(generationContext, null);
RuntimeHints runtimeHints = generationContext.getRuntimeHints();
assertThat(RuntimeHintsPredicates.reflection().onType(ComponentWithInnerAbstractClass.class)
.withMemberCategory(MemberCategory.DECLARED_CLASSES)).accepts(runtimeHints);
assertThat(RuntimeHintsPredicates.reflection().onType(ConcreteSerializer.class)
.withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(runtimeHints);
assertThat(RuntimeHintsPredicates.reflection().onType(AbstractSerializer.class)
.withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS).negate()).accepts(runtimeHints);
assertThat(RuntimeHintsPredicates.reflection().onType(NotSuitable.class)
.withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS).negate()).accepts(runtimeHints);
}
private void load(Class<?>... configs) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(configs);
@ -181,7 +210,7 @@ class JsonComponentModuleTests {
@JsonComponent
static class ComponentWithInnerAbstractClass {
static class AbstractSerializer extends NameAndAgeJsonComponent.Serializer {
static abstract class AbstractSerializer extends NameAndAgeJsonComponent.Serializer {
}
@ -189,6 +218,10 @@ class JsonComponentModuleTests {
}
static class NotSuitable {
}
}
@JsonComponent(scope = JsonComponent.Scope.KEYS)