Improve type discovery in Logback AOT contribution

Logback can infer the Java class to which an XML tag should be mapped
by looking for a setter method on the class to which the parent tag
was mapped. This commits ensures that reflection hints are added for
such classes.

Fixes gh-32839
This commit is contained in:
Andy Wilkinson
2022-10-21 16:05:30 +01:00
parent eebe23a081
commit b0b2818f0b
2 changed files with 124 additions and 17 deletions

View File

@@ -31,6 +31,7 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Stream;
import ch.qos.logback.classic.joran.JoranConfigurator;
@@ -38,6 +39,7 @@ import ch.qos.logback.core.Context;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.joran.spi.ElementSelector;
import ch.qos.logback.core.joran.spi.RuleStore;
import ch.qos.logback.core.joran.util.PropertySetter;
import ch.qos.logback.core.joran.util.beans.BeanDescription;
import ch.qos.logback.core.model.ComponentModel;
import ch.qos.logback.core.model.Model;
@@ -46,6 +48,7 @@ import ch.qos.logback.core.model.processor.DefaultProcessor;
import ch.qos.logback.core.model.processor.ModelInterpretationContext;
import ch.qos.logback.core.spi.ContextAware;
import ch.qos.logback.core.spi.ContextAwareBase;
import ch.qos.logback.core.util.AggregationType;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.MemberCategory;
@@ -62,6 +65,7 @@ import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.function.SingletonSupplier;
/**
* Extended version of the Logback {@link JoranConfigurator} that adds additional Spring
@@ -201,32 +205,64 @@ class SpringBootJoranConfigurator extends JoranConfigurator {
}
private Set<String> reflectionTypes(Model model) {
return reflectionTypes(model, () -> null);
}
private Set<String> reflectionTypes(Model model, Supplier<Object> parent) {
Set<String> reflectionTypes = new HashSet<>();
if (model instanceof ComponentModel) {
String className = ((ComponentModel) model).getClassName();
processComponent(className, reflectionTypes);
}
String tag = model.getTag();
if (tag != null) {
String componentType = this.modelInterpretationContext.getDefaultNestedComponentRegistry()
.findDefaultComponentTypeByTag(tag);
Class<?> componentType = determineType(model, parent);
if (componentType != null) {
processComponent(componentType, reflectionTypes);
}
Supplier<Object> componentSupplier = SingletonSupplier.ofNullable(() -> instantiate(componentType));
for (Model submodel : model.getSubModels()) {
reflectionTypes.addAll(reflectionTypes(submodel));
reflectionTypes.addAll(reflectionTypes(submodel, componentSupplier));
}
return reflectionTypes;
}
private void processComponent(String componentTypeName, Set<String> reflectionTypes) {
if (componentTypeName != null) {
componentTypeName = this.modelInterpretationContext.getImport(componentTypeName);
BeanDescription beanDescription = this.modelInterpretationContext.getBeanDescriptionCache()
.getBeanDescription(loadComponentType(componentTypeName));
reflectionTypes.addAll(parameterTypesNames(beanDescription.getPropertyNameToAdder().values()));
reflectionTypes.addAll(parameterTypesNames(beanDescription.getPropertyNameToSetter().values()));
reflectionTypes.add(componentTypeName);
private Class<?> determineType(Model model, Supplier<Object> parentSupplier) {
String className = null;
if (model instanceof ComponentModel) {
className = ((ComponentModel) model).getClassName();
}
if (className == null) {
String tag = model.getTag();
if (tag != null) {
className = this.modelInterpretationContext.getDefaultNestedComponentRegistry()
.findDefaultComponentTypeByTag(tag);
if (className == null) {
Class<?> type = inferTypeFromParent(parentSupplier, tag);
if (type != null) {
return type;
}
}
}
}
if (className != null) {
className = this.modelInterpretationContext.getImport(className);
return loadComponentType(className);
}
return null;
}
private Class<?> inferTypeFromParent(Supplier<Object> parentSupplier, String tag) {
Object parent = parentSupplier.get();
if (parent != null) {
try {
Class<?> typeFromSetter = new PropertySetter(
this.modelInterpretationContext.getBeanDescriptionCache(), parent)
.getClassNameViaImplicitRules(tag, AggregationType.AS_COMPLEX_PROPERTY,
this.modelInterpretationContext.getDefaultNestedComponentRegistry());
if (typeFromSetter != null) {
return typeFromSetter;
}
}
catch (Exception ex) {
// Continue
}
}
return null;
}
private Class<?> loadComponentType(String componentType) {
@@ -238,6 +274,23 @@ class SpringBootJoranConfigurator extends JoranConfigurator {
}
}
private Object instantiate(Class<?> type) {
try {
return type.getConstructor().newInstance();
}
catch (Exception ex) {
return null;
}
}
private void processComponent(Class<?> componentType, Set<String> reflectionTypes) {
BeanDescription beanDescription = this.modelInterpretationContext.getBeanDescriptionCache()
.getBeanDescription(componentType);
reflectionTypes.addAll(parameterTypesNames(beanDescription.getPropertyNameToAdder().values()));
reflectionTypes.addAll(parameterTypesNames(beanDescription.getPropertyNameToSetter().values()));
reflectionTypes.add(componentType.getCanonicalName());
}
private Collection<String> parameterTypesNames(Collection<Method> methods) {
return methods.stream()
.filter((method) -> !method.getDeclaringClass().equals(ContextAware.class)

View File

@@ -31,6 +31,7 @@ import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.Layout;
import ch.qos.logback.core.joran.spi.DefaultClass;
import ch.qos.logback.core.model.ComponentModel;
import ch.qos.logback.core.model.ImplicitModel;
import ch.qos.logback.core.model.ImportModel;
@@ -149,6 +150,34 @@ class LogbackConfigurationAotContributionTests {
.accepts(generationContext.getRuntimeHints());
}
@Test
void typeFromParentsSetterIsRegisteredForReflection() {
ImplicitModel implementation = new ImplicitModel();
implementation.setTag("implementation");
ComponentModel component = new ComponentModel();
component.setClassName(Outer.class.getName());
component.getSubModels().add(implementation);
TestGenerationContext generationContext = applyContribution(component);
assertThat(invokePublicConstructorsAndInspectAndInvokePublicMethodsOf(Outer.class))
.accepts(generationContext.getRuntimeHints());
assertThat(invokePublicConstructorsAndInspectAndInvokePublicMethodsOf(Implementation.class))
.accepts(generationContext.getRuntimeHints());
}
@Test
void typeFromParentsDefaultClassAnnotatedSetterIsRegisteredForReflection() {
ImplicitModel contract = new ImplicitModel();
contract.setTag("contract");
ComponentModel component = new ComponentModel();
component.setClassName(OuterWithDefaultClass.class.getName());
component.getSubModels().add(contract);
TestGenerationContext generationContext = applyContribution(component);
assertThat(invokePublicConstructorsAndInspectAndInvokePublicMethodsOf(OuterWithDefaultClass.class))
.accepts(generationContext.getRuntimeHints());
assertThat(invokePublicConstructorsAndInspectAndInvokePublicMethodsOf(Implementation.class))
.accepts(generationContext.getRuntimeHints());
}
private Predicate<RuntimeHints> invokePublicConstructorsOf(String name) {
return RuntimeHintsPredicates.reflection().onType(TypeReference.of(name))
.withMemberCategory(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
@@ -202,4 +231,29 @@ class LogbackConfigurationAotContributionTests {
}
}
public static class Outer {
public void setImplementation(Implementation implementation) {
}
}
public static class OuterWithDefaultClass {
@DefaultClass(Implementation.class)
public void setContract(Contract contract) {
}
}
public static class Implementation implements Contract {
}
public interface Contract {
}
}