Skip to content

Commit b0b2818

Browse files
committed
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
1 parent eebe23a commit b0b2818

File tree

2 files changed

+124
-17
lines changed

2 files changed

+124
-17
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/SpringBootJoranConfigurator.java

+70-17
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@
3131
import java.util.Map;
3232
import java.util.Properties;
3333
import java.util.Set;
34+
import java.util.function.Supplier;
3435
import java.util.stream.Stream;
3536

3637
import ch.qos.logback.classic.joran.JoranConfigurator;
3738
import ch.qos.logback.core.Context;
3839
import ch.qos.logback.core.CoreConstants;
3940
import ch.qos.logback.core.joran.spi.ElementSelector;
4041
import ch.qos.logback.core.joran.spi.RuleStore;
42+
import ch.qos.logback.core.joran.util.PropertySetter;
4143
import ch.qos.logback.core.joran.util.beans.BeanDescription;
4244
import ch.qos.logback.core.model.ComponentModel;
4345
import ch.qos.logback.core.model.Model;
@@ -46,6 +48,7 @@
4648
import ch.qos.logback.core.model.processor.ModelInterpretationContext;
4749
import ch.qos.logback.core.spi.ContextAware;
4850
import ch.qos.logback.core.spi.ContextAwareBase;
51+
import ch.qos.logback.core.util.AggregationType;
4952

5053
import org.springframework.aot.generate.GenerationContext;
5154
import org.springframework.aot.hint.MemberCategory;
@@ -62,6 +65,7 @@
6265
import org.springframework.core.io.support.PropertiesLoaderUtils;
6366
import org.springframework.util.ClassUtils;
6467
import org.springframework.util.ReflectionUtils;
68+
import org.springframework.util.function.SingletonSupplier;
6569

6670
/**
6771
* Extended version of the Logback {@link JoranConfigurator} that adds additional Spring
@@ -201,32 +205,64 @@ private Set<Class<? extends Serializable>> serializationTypes(Model model) {
201205
}
202206

203207
private Set<String> reflectionTypes(Model model) {
208+
return reflectionTypes(model, () -> null);
209+
}
210+
211+
private Set<String> reflectionTypes(Model model, Supplier<Object> parent) {
204212
Set<String> reflectionTypes = new HashSet<>();
205-
if (model instanceof ComponentModel) {
206-
String className = ((ComponentModel) model).getClassName();
207-
processComponent(className, reflectionTypes);
208-
}
209-
String tag = model.getTag();
210-
if (tag != null) {
211-
String componentType = this.modelInterpretationContext.getDefaultNestedComponentRegistry()
212-
.findDefaultComponentTypeByTag(tag);
213+
Class<?> componentType = determineType(model, parent);
214+
if (componentType != null) {
213215
processComponent(componentType, reflectionTypes);
214216
}
217+
Supplier<Object> componentSupplier = SingletonSupplier.ofNullable(() -> instantiate(componentType));
215218
for (Model submodel : model.getSubModels()) {
216-
reflectionTypes.addAll(reflectionTypes(submodel));
219+
reflectionTypes.addAll(reflectionTypes(submodel, componentSupplier));
217220
}
218221
return reflectionTypes;
219222
}
220223

221-
private void processComponent(String componentTypeName, Set<String> reflectionTypes) {
222-
if (componentTypeName != null) {
223-
componentTypeName = this.modelInterpretationContext.getImport(componentTypeName);
224-
BeanDescription beanDescription = this.modelInterpretationContext.getBeanDescriptionCache()
225-
.getBeanDescription(loadComponentType(componentTypeName));
226-
reflectionTypes.addAll(parameterTypesNames(beanDescription.getPropertyNameToAdder().values()));
227-
reflectionTypes.addAll(parameterTypesNames(beanDescription.getPropertyNameToSetter().values()));
228-
reflectionTypes.add(componentTypeName);
224+
private Class<?> determineType(Model model, Supplier<Object> parentSupplier) {
225+
String className = null;
226+
if (model instanceof ComponentModel) {
227+
className = ((ComponentModel) model).getClassName();
228+
}
229+
if (className == null) {
230+
String tag = model.getTag();
231+
if (tag != null) {
232+
className = this.modelInterpretationContext.getDefaultNestedComponentRegistry()
233+
.findDefaultComponentTypeByTag(tag);
234+
if (className == null) {
235+
Class<?> type = inferTypeFromParent(parentSupplier, tag);
236+
if (type != null) {
237+
return type;
238+
}
239+
}
240+
}
241+
}
242+
if (className != null) {
243+
className = this.modelInterpretationContext.getImport(className);
244+
return loadComponentType(className);
245+
}
246+
return null;
247+
}
248+
249+
private Class<?> inferTypeFromParent(Supplier<Object> parentSupplier, String tag) {
250+
Object parent = parentSupplier.get();
251+
if (parent != null) {
252+
try {
253+
Class<?> typeFromSetter = new PropertySetter(
254+
this.modelInterpretationContext.getBeanDescriptionCache(), parent)
255+
.getClassNameViaImplicitRules(tag, AggregationType.AS_COMPLEX_PROPERTY,
256+
this.modelInterpretationContext.getDefaultNestedComponentRegistry());
257+
if (typeFromSetter != null) {
258+
return typeFromSetter;
259+
}
260+
}
261+
catch (Exception ex) {
262+
// Continue
263+
}
229264
}
265+
return null;
230266
}
231267

232268
private Class<?> loadComponentType(String componentType) {
@@ -238,6 +274,23 @@ private Class<?> loadComponentType(String componentType) {
238274
}
239275
}
240276

277+
private Object instantiate(Class<?> type) {
278+
try {
279+
return type.getConstructor().newInstance();
280+
}
281+
catch (Exception ex) {
282+
return null;
283+
}
284+
}
285+
286+
private void processComponent(Class<?> componentType, Set<String> reflectionTypes) {
287+
BeanDescription beanDescription = this.modelInterpretationContext.getBeanDescriptionCache()
288+
.getBeanDescription(componentType);
289+
reflectionTypes.addAll(parameterTypesNames(beanDescription.getPropertyNameToAdder().values()));
290+
reflectionTypes.addAll(parameterTypesNames(beanDescription.getPropertyNameToSetter().values()));
291+
reflectionTypes.add(componentType.getCanonicalName());
292+
}
293+
241294
private Collection<String> parameterTypesNames(Collection<Method> methods) {
242295
return methods.stream()
243296
.filter((method) -> !method.getDeclaringClass().equals(ContextAware.class)

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackConfigurationAotContributionTests.java

+54
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import ch.qos.logback.core.CoreConstants;
3232
import ch.qos.logback.core.FileAppender;
3333
import ch.qos.logback.core.Layout;
34+
import ch.qos.logback.core.joran.spi.DefaultClass;
3435
import ch.qos.logback.core.model.ComponentModel;
3536
import ch.qos.logback.core.model.ImplicitModel;
3637
import ch.qos.logback.core.model.ImportModel;
@@ -149,6 +150,34 @@ void componentModelReferencingImportedClassNameIsRegisteredForReflection() {
149150
.accepts(generationContext.getRuntimeHints());
150151
}
151152

153+
@Test
154+
void typeFromParentsSetterIsRegisteredForReflection() {
155+
ImplicitModel implementation = new ImplicitModel();
156+
implementation.setTag("implementation");
157+
ComponentModel component = new ComponentModel();
158+
component.setClassName(Outer.class.getName());
159+
component.getSubModels().add(implementation);
160+
TestGenerationContext generationContext = applyContribution(component);
161+
assertThat(invokePublicConstructorsAndInspectAndInvokePublicMethodsOf(Outer.class))
162+
.accepts(generationContext.getRuntimeHints());
163+
assertThat(invokePublicConstructorsAndInspectAndInvokePublicMethodsOf(Implementation.class))
164+
.accepts(generationContext.getRuntimeHints());
165+
}
166+
167+
@Test
168+
void typeFromParentsDefaultClassAnnotatedSetterIsRegisteredForReflection() {
169+
ImplicitModel contract = new ImplicitModel();
170+
contract.setTag("contract");
171+
ComponentModel component = new ComponentModel();
172+
component.setClassName(OuterWithDefaultClass.class.getName());
173+
component.getSubModels().add(contract);
174+
TestGenerationContext generationContext = applyContribution(component);
175+
assertThat(invokePublicConstructorsAndInspectAndInvokePublicMethodsOf(OuterWithDefaultClass.class))
176+
.accepts(generationContext.getRuntimeHints());
177+
assertThat(invokePublicConstructorsAndInspectAndInvokePublicMethodsOf(Implementation.class))
178+
.accepts(generationContext.getRuntimeHints());
179+
}
180+
152181
private Predicate<RuntimeHints> invokePublicConstructorsOf(String name) {
153182
return RuntimeHintsPredicates.reflection().onType(TypeReference.of(name))
154183
.withMemberCategory(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
@@ -202,4 +231,29 @@ private void withSystemProperty(String name, String value, Runnable action) {
202231
}
203232
}
204233

234+
public static class Outer {
235+
236+
public void setImplementation(Implementation implementation) {
237+
238+
}
239+
240+
}
241+
242+
public static class OuterWithDefaultClass {
243+
244+
@DefaultClass(Implementation.class)
245+
public void setContract(Contract contract) {
246+
247+
}
248+
249+
}
250+
251+
public static class Implementation implements Contract {
252+
253+
}
254+
255+
public interface Contract {
256+
257+
}
258+
205259
}

0 commit comments

Comments
 (0)