From a2d4ecf8ac80acf66c8ed99ae4d335ae3a288472 Mon Sep 17 00:00:00 2001 From: "Piotr P. Karwasz" Date: Sat, 12 Jul 2025 17:16:37 +0200 Subject: [PATCH 1/2] Add runtime hints for Log4j Core 2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change introduces runtime hints for Log4j Core 2 to support its integration with Spring Boot native images. Starting with Log4j 2.25.x, Log4j includes built-in GraalVM reachability metadata, allowing native image generation without requiring additional manual configuration. This contribution complements that by adding Spring Boot–specific metadata: * Registers default Spring Boot configuration files. * Registers classes referenced via `ClassUtils.isPresent(...)` checks. See gh-46410 Signed-off-by: Piotr P. Karwasz --- core/spring-boot/build.gradle | 3 + .../logging/log4j2/Log4J2LoggingSystem.java | 30 +++++--- .../logging/log4j2/Log4J2RuntimeHints.java | 67 +++++++++++++++++ .../resources/META-INF/spring/aot.factories | 1 + .../log4j2/Log4J2RuntimeHintsTests.java | 73 +++++++++++++++++++ 5 files changed, 163 insertions(+), 11 deletions(-) create mode 100644 core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2RuntimeHints.java create mode 100644 core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2RuntimeHintsTests.java diff --git a/core/spring-boot/build.gradle b/core/spring-boot/build.gradle index 5b6c9fb4ab5..86799c5f475 100644 --- a/core/spring-boot/build.gradle +++ b/core/spring-boot/build.gradle @@ -71,6 +71,9 @@ dependencies { testImplementation("org.hibernate.validator:hibernate-validator") testImplementation("org.jboss.logging:jboss-logging") testImplementation("org.springframework.data:spring-data-r2dbc") + + // Used in Log4J2RuntimeHintsTests + testRuntimeOnly("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml") } def syncJavaTemplates = tasks.register("syncJavaTemplates", Sync) { diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java index 6e941a3e167..bea63d57a73 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2LoggingSystem.java @@ -90,44 +90,50 @@ public class Log4J2LoggingSystem extends AbstractLoggingSystem { private static final String OPTIONAL_PREFIX = "optional:"; - private static final String LOG4J_BRIDGE_HANDLER = "org.apache.logging.log4j.jul.Log4jBridgeHandler"; + /** + * JUL handler that routes messages to the Log4j API (optional dependency). + */ + static final String LOG4J_BRIDGE_HANDLER = "org.apache.logging.log4j.jul.Log4jBridgeHandler"; - private static final String LOG4J_LOG_MANAGER = "org.apache.logging.log4j.jul.LogManager"; + /** + * JUL LogManager that routes messages to the Log4j API as the backend. + */ + static final String LOG4J_LOG_MANAGER = "org.apache.logging.log4j.jul.LogManager"; /** * JSON tree parser used by Log4j 2 (optional dependency). */ - private static final String JSON_TREE_PARSER_V2 = "com.fasterxml.jackson.databind.ObjectMapper"; + static final String JSON_TREE_PARSER_V2 = "com.fasterxml.jackson.databind.ObjectMapper"; /** * JSON tree parser embedded in Log4j 3. */ - private static final String JSON_TREE_PARSER_V3 = "org.apache.logging.log4j.kit.json.JsonReader"; + static final String JSON_TREE_PARSER_V3 = "org.apache.logging.log4j.kit.json.JsonReader"; /** * Configuration factory for properties files (Log4j 2). */ - private static final String PROPS_CONFIGURATION_FACTORY_V2 = "org.apache.logging.log4j.core.config.properties.PropertiesConfigurationFactory"; + static final String PROPS_CONFIGURATION_FACTORY_V2 = "org.apache.logging.log4j.core.config.properties.PropertiesConfigurationFactory"; /** * Configuration factory for properties files (Log4j 3, optional dependency). */ - private static final String PROPS_CONFIGURATION_FACTORY_V3 = "org.apache.logging.log4j.config.properties.JavaPropsConfigurationFactory"; + static final String PROPS_CONFIGURATION_FACTORY_V3 = "org.apache.logging.log4j.config.properties.JavaPropsConfigurationFactory"; /** * YAML tree parser used by Log4j 2 (optional dependency). */ - private static final String YAML_TREE_PARSER_V2 = "com.fasterxml.jackson.dataformat.yaml.YAMLMapper"; + static final String YAML_TREE_PARSER_V2 = "com.fasterxml.jackson.dataformat.yaml.YAMLMapper"; /** * Configuration factory for YAML files (Log4j 2, embedded). */ - private static final String YAML_CONFIGURATION_FACTORY_V2 = "org.apache.logging.log4j.core.config.yaml.YamlConfigurationFactory"; + static final String YAML_CONFIGURATION_FACTORY_V2 = "org.apache.logging.log4j.core.config.yaml.YamlConfigurationFactory"; /** * Configuration factory for YAML files (Log4j 3, optional dependency). */ - private static final String YAML_CONFIGURATION_FACTORY_V3 = "org.apache.logging.log4j.config.yaml.YamlConfigurationFactory"; + static final String YAML_CONFIGURATION_FACTORY_V3 = "org.apache.logging.log4j.config.yaml.YamlConfigurationFactory"; private static final SpringEnvironmentPropertySource propertySource = new SpringEnvironmentPropertySource(); @@ -616,8 +622,10 @@ public class Log4J2LoggingSystem extends AbstractLoggingSystem { @Order(0) public static class Factory implements LoggingSystemFactory { - private static final boolean PRESENT = ClassUtils - .isPresent("org.apache.logging.log4j.core.impl.Log4jContextFactory", Factory.class.getClassLoader()); + static final String LOG4J_CORE_CONTEXT_FACTORY = "org.apache.logging.log4j.core.impl.Log4jContextFactory"; + + private static final boolean PRESENT = ClassUtils.isPresent(LOG4J_CORE_CONTEXT_FACTORY, + Factory.class.getClassLoader()); @Override public @Nullable LoggingSystem getLoggingSystem(ClassLoader classLoader) { diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2RuntimeHints.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2RuntimeHints.java new file mode 100644 index 00000000000..ad2739c7fe9 --- /dev/null +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2RuntimeHints.java @@ -0,0 +1,67 @@ +/* + * Copyright 2025-present 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.log4j2; + +import org.jspecify.annotations.Nullable; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.util.ClassUtils; + +/** + * {@link RuntimeHintsRegistrar} implementation for {@link Log4J2LoggingSystem}. + * + * @author Piotr P. Karwasz + */ +class Log4J2RuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { + if (!ClassUtils.isPresent(Log4J2LoggingSystem.Factory.LOG4J_CORE_CONTEXT_FACTORY, classLoader)) { + return; + } + registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.Factory.LOG4J_CORE_CONTEXT_FACTORY); + // Register default Log4j2 configuration files + hints.resources().registerPattern("org/springframework/boot/logging/log4j2/log4j2.xml"); + hints.resources().registerPattern("org/springframework/boot/logging/log4j2/log4j2-file.xml"); + hints.resources().registerPattern("log4j2.springboot"); + // Declares the types that Log4j2LoggingSystem checks for existence reflectively. + registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.JSON_TREE_PARSER_V2); + registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.JSON_TREE_PARSER_V3); + registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.PROPS_CONFIGURATION_FACTORY_V2); + registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.PROPS_CONFIGURATION_FACTORY_V3); + registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.YAML_TREE_PARSER_V2); + registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.YAML_CONFIGURATION_FACTORY_V2); + registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.YAML_CONFIGURATION_FACTORY_V3); + // Register JUL to Log4j 2 bridge handler + registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.LOG4J_BRIDGE_HANDLER); + registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.LOG4J_LOG_MANAGER); + // Don't need to register the custom Log4j 2 plugins, + // since they will be registered by the Log4j 2 `GraalvmPluginProcessor`. + } + + /** + * Registers the type to prevent GraalVM from removing it during the native build. + * @param hints the runtime hints to register with + * @param classLoader the class loader to use for type resolution + * @param typeName the name of the type to register + */ + private void registerTypeForReachability(RuntimeHints hints, @Nullable ClassLoader classLoader, String typeName) { + hints.reflection().registerTypeIfPresent(classLoader, typeName); + } + +} diff --git a/core/spring-boot/src/main/resources/META-INF/spring/aot.factories b/core/spring-boot/src/main/resources/META-INF/spring/aot.factories index b661eb51c41..4a0efd5e6c6 100644 --- a/core/spring-boot/src/main/resources/META-INF/spring/aot.factories +++ b/core/spring-boot/src/main/resources/META-INF/spring/aot.factories @@ -6,6 +6,7 @@ org.springframework.boot.context.config.ConfigDataLocationRuntimeHints,\ org.springframework.boot.context.config.ConfigDataPropertiesRuntimeHints,\ org.springframework.boot.env.PropertySourceRuntimeHints,\ org.springframework.boot.logging.java.JavaLoggingSystemRuntimeHints,\ +org.springframework.boot.logging.log4j2.Log4J2RuntimeHints,\ org.springframework.boot.logging.logback.LogbackRuntimeHints,\ org.springframework.boot.logging.structured.ElasticCommonSchemaProperties$ElasticCommonSchemaPropertiesRuntimeHints,\ org.springframework.boot.logging.structured.GraylogExtendedLogFormatProperties$GraylogExtendedLogFormatPropertiesRuntimeHints,\ diff --git a/core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2RuntimeHintsTests.java b/core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2RuntimeHintsTests.java new file mode 100644 index 00000000000..0af647c10df --- /dev/null +++ b/core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2RuntimeHintsTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012-present 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.log4j2; + +import org.apache.logging.log4j.core.impl.Log4jContextFactory; +import org.apache.logging.log4j.jul.Log4jBridgeHandler; +import org.apache.logging.log4j.jul.LogManager; +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.ReflectionHints; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.TypeReference; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link Log4J2RuntimeHints}. + * + * @author Piotr P. Karwasz + */ +class Log4J2RuntimeHintsTests { + + @Test + void registersHintsForTypesCheckedByLog4J2LoggingSystem() { + ReflectionHints reflection = registerHints(); + // Once Log4j Core is reachable, GraalVM will automatically + // add reachability metadata embedded in the Log4j Core jar and extensions. + assertThat(reflection.getTypeHint(Log4jContextFactory.class)).isNotNull(); + assertThat(reflection.getTypeHint(Log4jBridgeHandler.class)).isNotNull(); + assertThat(reflection.getTypeHint(LogManager.class)).isNotNull(); + } + + /** + * + */ + @Test + void registersHintsForConfigurationFileParsers() { + ReflectionHints reflection = registerHints(); + // JSON + assertThat(reflection.getTypeHint(TypeReference.of("com.fasterxml.jackson.databind.ObjectMapper"))).isNotNull(); + // YAML + assertThat(reflection.getTypeHint(TypeReference.of("com.fasterxml.jackson.dataformat.yaml.YAMLMapper"))) + .isNotNull(); + } + + @Test + void doesNotRegisterHintsWhenLog4jCoreIsNotAvailable() { + RuntimeHints hints = new RuntimeHints(); + new Log4J2RuntimeHints().registerHints(hints, ClassLoader.getPlatformClassLoader()); + assertThat(hints.reflection().typeHints()).isEmpty(); + } + + private ReflectionHints registerHints() { + RuntimeHints hints = new RuntimeHints(); + new Log4J2RuntimeHints().registerHints(hints, getClass().getClassLoader()); + return hints.reflection(); + } + +} From e20e2393c900424361b445a5f3387241ff3fa6eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Tue, 7 Oct 2025 17:07:34 +0200 Subject: [PATCH 2/2] Polish "Add runtime hints for Log4j Core 2" See gh-46410 --- core/spring-boot/build.gradle | 3 - .../logging/log4j2/Log4J2RuntimeHints.java | 43 +++++------ .../log4j2/Log4J2RuntimeHintsTests.java | 73 +++++++++++++------ 3 files changed, 67 insertions(+), 52 deletions(-) diff --git a/core/spring-boot/build.gradle b/core/spring-boot/build.gradle index 86799c5f475..5b6c9fb4ab5 100644 --- a/core/spring-boot/build.gradle +++ b/core/spring-boot/build.gradle @@ -71,9 +71,6 @@ dependencies { testImplementation("org.hibernate.validator:hibernate-validator") testImplementation("org.jboss.logging:jboss-logging") testImplementation("org.springframework.data:spring-data-r2dbc") - - // Used in Log4J2RuntimeHintsTests - testRuntimeOnly("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml") } def syncJavaTemplates = tasks.register("syncJavaTemplates", Sync) { diff --git a/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2RuntimeHints.java b/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2RuntimeHints.java index ad2739c7fe9..05fdf00326a 100644 --- a/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2RuntimeHints.java +++ b/core/spring-boot/src/main/java/org/springframework/boot/logging/log4j2/Log4J2RuntimeHints.java @@ -1,5 +1,5 @@ /* - * Copyright 2025-present the original author or authors. + * Copyright 2012-present 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. @@ -26,42 +26,33 @@ import org.springframework.util.ClassUtils; * {@link RuntimeHintsRegistrar} implementation for {@link Log4J2LoggingSystem}. * * @author Piotr P. Karwasz + * @author Stephane Nicoll */ class Log4J2RuntimeHints implements RuntimeHintsRegistrar { @Override public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) { - if (!ClassUtils.isPresent(Log4J2LoggingSystem.Factory.LOG4J_CORE_CONTEXT_FACTORY, classLoader)) { - return; + if (ClassUtils.isPresent(Log4J2LoggingSystem.Factory.LOG4J_CORE_CONTEXT_FACTORY, classLoader)) { + registerLog4j2Hints(hints, classLoader); } - registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.Factory.LOG4J_CORE_CONTEXT_FACTORY); + } + + private void registerLog4j2Hints(RuntimeHints hints, @Nullable ClassLoader classLoader) { + hints.reflection().registerTypeIfPresent(classLoader, Log4J2LoggingSystem.Factory.LOG4J_CORE_CONTEXT_FACTORY); // Register default Log4j2 configuration files hints.resources().registerPattern("org/springframework/boot/logging/log4j2/log4j2.xml"); hints.resources().registerPattern("org/springframework/boot/logging/log4j2/log4j2-file.xml"); hints.resources().registerPattern("log4j2.springboot"); // Declares the types that Log4j2LoggingSystem checks for existence reflectively. - registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.JSON_TREE_PARSER_V2); - registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.JSON_TREE_PARSER_V3); - registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.PROPS_CONFIGURATION_FACTORY_V2); - registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.PROPS_CONFIGURATION_FACTORY_V3); - registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.YAML_TREE_PARSER_V2); - registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.YAML_CONFIGURATION_FACTORY_V2); - registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.YAML_CONFIGURATION_FACTORY_V3); - // Register JUL to Log4j 2 bridge handler - registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.LOG4J_BRIDGE_HANDLER); - registerTypeForReachability(hints, classLoader, Log4J2LoggingSystem.LOG4J_LOG_MANAGER); - // Don't need to register the custom Log4j 2 plugins, - // since they will be registered by the Log4j 2 `GraalvmPluginProcessor`. - } - - /** - * Registers the type to prevent GraalVM from removing it during the native build. - * @param hints the runtime hints to register with - * @param classLoader the class loader to use for type resolution - * @param typeName the name of the type to register - */ - private void registerTypeForReachability(RuntimeHints hints, @Nullable ClassLoader classLoader, String typeName) { - hints.reflection().registerTypeIfPresent(classLoader, typeName); + hints.reflection().registerTypeIfPresent(classLoader, Log4J2LoggingSystem.JSON_TREE_PARSER_V2); + hints.reflection().registerTypeIfPresent(classLoader, Log4J2LoggingSystem.JSON_TREE_PARSER_V3); + hints.reflection().registerTypeIfPresent(classLoader, Log4J2LoggingSystem.PROPS_CONFIGURATION_FACTORY_V2); + hints.reflection().registerTypeIfPresent(classLoader, Log4J2LoggingSystem.PROPS_CONFIGURATION_FACTORY_V3); + hints.reflection().registerTypeIfPresent(classLoader, Log4J2LoggingSystem.YAML_TREE_PARSER_V2); + hints.reflection().registerTypeIfPresent(classLoader, Log4J2LoggingSystem.YAML_CONFIGURATION_FACTORY_V2); + hints.reflection().registerTypeIfPresent(classLoader, Log4J2LoggingSystem.YAML_CONFIGURATION_FACTORY_V3); + hints.reflection().registerTypeIfPresent(classLoader, Log4J2LoggingSystem.LOG4J_BRIDGE_HANDLER); + hints.reflection().registerTypeIfPresent(classLoader, Log4J2LoggingSystem.LOG4J_LOG_MANAGER); } } diff --git a/core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2RuntimeHintsTests.java b/core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2RuntimeHintsTests.java index 0af647c10df..c2389a26c99 100644 --- a/core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2RuntimeHintsTests.java +++ b/core/spring-boot/src/test/java/org/springframework/boot/logging/log4j2/Log4J2RuntimeHintsTests.java @@ -16,14 +16,21 @@ package org.springframework.boot.logging.log4j2; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Arrays; + +import org.apache.logging.log4j.core.config.properties.PropertiesConfigurationFactory; +import org.apache.logging.log4j.core.config.yaml.YamlConfigurationFactory; import org.apache.logging.log4j.core.impl.Log4jContextFactory; import org.apache.logging.log4j.jul.Log4jBridgeHandler; import org.apache.logging.log4j.jul.LogManager; import org.junit.jupiter.api.Test; -import org.springframework.aot.hint.ReflectionHints; import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.TypeReference; +import org.springframework.aot.hint.predicate.ReflectionHintsPredicates; +import org.springframework.aot.hint.predicate.ResourceHintsPredicates; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import static org.assertj.core.api.Assertions.assertThat; @@ -31,43 +38,63 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link Log4J2RuntimeHints}. * * @author Piotr P. Karwasz + * @author Stephane Nicoll */ class Log4J2RuntimeHintsTests { + private static final ReflectionHintsPredicates reflectionHints = RuntimeHintsPredicates.reflection(); + + private static final ResourceHintsPredicates resourceHints = RuntimeHintsPredicates.resource(); + @Test void registersHintsForTypesCheckedByLog4J2LoggingSystem() { - ReflectionHints reflection = registerHints(); - // Once Log4j Core is reachable, GraalVM will automatically - // add reachability metadata embedded in the Log4j Core jar and extensions. - assertThat(reflection.getTypeHint(Log4jContextFactory.class)).isNotNull(); - assertThat(reflection.getTypeHint(Log4jBridgeHandler.class)).isNotNull(); - assertThat(reflection.getTypeHint(LogManager.class)).isNotNull(); + RuntimeHints runtimeHints = registerHints(); + assertThat(reflectionHints.onType(Log4jContextFactory.class)).accepts(runtimeHints); + assertThat(reflectionHints.onType(Log4jBridgeHandler.class)).accepts(runtimeHints); + assertThat(reflectionHints.onType(LogManager.class)).accepts(runtimeHints); + assertThat(reflectionHints.onType(PropertiesConfigurationFactory.class)).accepts(runtimeHints); + assertThat(reflectionHints.onType(YamlConfigurationFactory.class)).accepts(runtimeHints); } - /** - * - */ @Test - void registersHintsForConfigurationFileParsers() { - ReflectionHints reflection = registerHints(); - // JSON - assertThat(reflection.getTypeHint(TypeReference.of("com.fasterxml.jackson.databind.ObjectMapper"))).isNotNull(); - // YAML - assertThat(reflection.getTypeHint(TypeReference.of("com.fasterxml.jackson.dataformat.yaml.YAMLMapper"))) - .isNotNull(); + void registersHintsForLog4j2DefaultConfigurationFiles() { + RuntimeHints runtimeHints = registerHints(); + assertThat(resourceHints.forResource("org/springframework/boot/logging/log4j2/log4j2.xml")) + .accepts(runtimeHints); + assertThat(resourceHints.forResource("org/springframework/boot/logging/log4j2/log4j2-file.xml")) + .accepts(runtimeHints); } @Test void doesNotRegisterHintsWhenLog4jCoreIsNotAvailable() { - RuntimeHints hints = new RuntimeHints(); - new Log4J2RuntimeHints().registerHints(hints, ClassLoader.getPlatformClassLoader()); - assertThat(hints.reflection().typeHints()).isEmpty(); + RuntimeHints runtimeHints = new RuntimeHints(); + new Log4J2RuntimeHints().registerHints(runtimeHints, new HidePackagesClassLoader("org.apache.logging.log4j")); + assertThat(runtimeHints.reflection().typeHints()).isEmpty(); } - private ReflectionHints registerHints() { + private RuntimeHints registerHints() { RuntimeHints hints = new RuntimeHints(); new Log4J2RuntimeHints().registerHints(hints, getClass().getClassLoader()); - return hints.reflection(); + return hints; + } + + static final class HidePackagesClassLoader extends URLClassLoader { + + private final String[] hiddenPackages; + + HidePackagesClassLoader(String... hiddenPackages) { + super(new URL[0], HidePackagesClassLoader.class.getClassLoader()); + this.hiddenPackages = hiddenPackages; + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (Arrays.stream(this.hiddenPackages).anyMatch(name::startsWith)) { + throw new ClassNotFoundException(); + } + return super.loadClass(name, resolve); + } + } }