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:
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user