Register reflection hints for Logback

Closes gh-30975
This commit is contained in:
Andy Wilkinson 2022-05-11 12:34:47 +01:00
parent 0ce6e437a9
commit f6225fe98b
3 changed files with 231 additions and 0 deletions

View File

@ -0,0 +1,108 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.logging.logback;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.pattern.CallerDataConverter;
import ch.qos.logback.classic.pattern.ClassOfCallerConverter;
import ch.qos.logback.classic.pattern.ContextNameConverter;
import ch.qos.logback.classic.pattern.DateConverter;
import ch.qos.logback.classic.pattern.ExtendedThrowableProxyConverter;
import ch.qos.logback.classic.pattern.FileOfCallerConverter;
import ch.qos.logback.classic.pattern.LevelConverter;
import ch.qos.logback.classic.pattern.LineOfCallerConverter;
import ch.qos.logback.classic.pattern.LineSeparatorConverter;
import ch.qos.logback.classic.pattern.LocalSequenceNumberConverter;
import ch.qos.logback.classic.pattern.LoggerConverter;
import ch.qos.logback.classic.pattern.MDCConverter;
import ch.qos.logback.classic.pattern.MarkerConverter;
import ch.qos.logback.classic.pattern.MessageConverter;
import ch.qos.logback.classic.pattern.MethodOfCallerConverter;
import ch.qos.logback.classic.pattern.NopThrowableInformationConverter;
import ch.qos.logback.classic.pattern.PropertyConverter;
import ch.qos.logback.classic.pattern.RelativeTimeConverter;
import ch.qos.logback.classic.pattern.RootCauseFirstThrowableProxyConverter;
import ch.qos.logback.classic.pattern.SyslogStartConverter;
import ch.qos.logback.classic.pattern.ThreadConverter;
import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
import ch.qos.logback.core.rolling.helper.DateTokenConverter;
import ch.qos.logback.core.rolling.helper.IntegerTokenConverter;
import org.slf4j.bridge.SLF4JBridgeHandler;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeHint.Builder;
import org.springframework.aot.hint.TypeReference;
import org.springframework.util.ClassUtils;
/**
* {@link RuntimeHintsRegistrar} for Logback.
*
* @author Andy Wilkinson
*/
class LogbackRuntimeHintsRegistrar implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
if (!ClassUtils.isPresent("ch.qos.logback.classic.LoggerContext", classLoader)) {
return;
}
ReflectionHints reflection = hints.reflection();
registerHintsForLogbackLoggingSystemTypeChecks(reflection);
registerHintsForBuiltInLogbackConverters(reflection);
registerHintsForSpringBootConverters(reflection);
}
private void registerHintsForLogbackLoggingSystemTypeChecks(ReflectionHints reflection) {
Consumer<Builder> defaultHint = (hint) -> {
};
reflection.registerType(LoggerContext.class, defaultHint);
reflection.registerType(SLF4JBridgeHandler.class, defaultHint);
}
private void registerHintsForBuiltInLogbackConverters(ReflectionHints reflection) {
registerForPublicConstructorInvocation(reflection, CallerDataConverter.class, ClassOfCallerConverter.class,
ContextNameConverter.class, DateConverter.class, DateTokenConverter.class,
ExtendedThrowableProxyConverter.class, FileOfCallerConverter.class, IntegerTokenConverter.class,
LevelConverter.class, LineOfCallerConverter.class, LineSeparatorConverter.class,
LocalSequenceNumberConverter.class, LoggerConverter.class, MarkerConverter.class, MDCConverter.class,
MessageConverter.class, MethodOfCallerConverter.class, NopThrowableInformationConverter.class,
PropertyConverter.class, RelativeTimeConverter.class, RootCauseFirstThrowableProxyConverter.class,
SyslogStartConverter.class, ThreadConverter.class, ThrowableProxyConverter.class);
}
private void registerHintsForSpringBootConverters(ReflectionHints reflection) {
registerForPublicConstructorInvocation(reflection, ColorConverter.class,
ExtendedWhitespaceThrowableProxyConverter.class, WhitespaceThrowableProxyConverter.class);
}
private void registerForPublicConstructorInvocation(ReflectionHints reflection, Class<?>... classes) {
reflection.registerTypes(typeReferences(classes),
(hint) -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
}
private Iterable<TypeReference> typeReferences(Class<?>... classes) {
return Stream.of(classes).map(TypeReference::of).collect(Collectors.toList());
}
}

View File

@ -1,2 +1,4 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.boot.logging.logback.LogbackRuntimeHintsRegistrar
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
org.springframework.boot.context.properties.ConfigurationPropertiesBeanFactoryInitializationAotProcessor

View File

@ -0,0 +1,121 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.logging.logback;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.pattern.ClassicConverter;
import org.junit.jupiter.api.Test;
import org.slf4j.bridge.SLF4JBridgeHandler;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.ReflectionHints;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeHint;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.ClassUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link LogbackRuntimeHintsRegistrar}.
*
* @author Andy Wilkinson
*/
class LogbackRuntimeHintsRegistrarTests {
@Test
void registersHintsForTypesCheckedByLogbackLoggingSystem() throws Exception {
ReflectionHints reflection = registerHints();
assertThat(reflection.getTypeHint(LoggerContext.class)).isNotNull();
assertThat(reflection.getTypeHint(SLF4JBridgeHandler.class)).isNotNull();
}
@Test
void registersHintsForBuiltInLogbackConverters() throws Exception {
RuntimeHints hints = new RuntimeHints();
new LogbackRuntimeHintsRegistrar().registerHints(hints, getClass().getClassLoader());
ReflectionHints reflection = registerHints();
assertThat(logbackConverters()).allSatisfy(registeredForPublicConstructorInvocation(reflection));
}
@Test
void registersHintsForSpringBootConverters() throws IOException, ClassNotFoundException, LinkageError {
ReflectionHints reflection = registerHints();
assertThat(List.of(ColorConverter.class, ExtendedWhitespaceThrowableProxyConverter.class,
WhitespaceThrowableProxyConverter.class))
.allSatisfy(registeredForPublicConstructorInvocation(reflection));
}
@Test
void doesNotRegisterHintsWhenLoggerContextIsNotAvailable() {
RuntimeHints hints = new RuntimeHints();
new LogbackRuntimeHintsRegistrar().registerHints(hints, ClassLoader.getPlatformClassLoader());
assertThat(hints.reflection().typeHints()).isEmpty();
}
private ReflectionHints registerHints() {
RuntimeHints hints = new RuntimeHints();
new LogbackRuntimeHintsRegistrar().registerHints(hints, getClass().getClassLoader());
ReflectionHints reflection = hints.reflection();
return reflection;
}
private Consumer<Class<?>> registeredForPublicConstructorInvocation(ReflectionHints reflection) {
return (converter) -> {
TypeHint typeHint = reflection.getTypeHint(converter);
assertThat(typeHint).isNotNull();
assertThat(typeHint.getMemberCategories()).containsExactly(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
};
}
private List<Class<?>> logbackConverters() throws IOException {
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] converterResources = resolver
.getResources("classpath:ch/qos/logback/classic/pattern/*Converter.class");
return Stream.of(converterResources).map(this::className).map(this::load).filter(this::isConcreteConverter)
.collect(Collectors.toList());
}
private String className(Resource resource) {
String filename = resource.getFilename();
filename = filename.substring(0, filename.length() - ".class".length());
return "ch.qos.logback.classic.pattern." + filename;
}
private Class<?> load(String className) {
try {
return ClassUtils.forName(className, getClass().getClassLoader());
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private boolean isConcreteConverter(Class<?> candidate) {
return ClassicConverter.class.isAssignableFrom(candidate) && !Modifier.isAbstract(candidate.getModifiers());
}
}