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] 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); + } + } }