From dd272a90dd4c783e8e998af2f97e1d432b1bf04b Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Tue, 10 Mar 2026 18:05:50 +0100 Subject: [PATCH 1/2] refactor: Use spring boot api instead of custom extensions to manage unresolved placeholders in properties WIP - Remove custom class extension with acces to non-public spring boot classes --- ...mentPreparedEventForPropertiesLogging.java | 194 ------------------ .../propertieslogger/PropertiesLogger.java | 19 +- .../PropertiesLoggerTest.java | 2 +- ...xternalPropertiesFilesIntegrationTest.java | 3 +- .../examples/WithEnvironmentVariableTest.java | 2 +- 5 files changed, 14 insertions(+), 206 deletions(-) delete mode 100644 src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/EnvironmentPreparedEventForPropertiesLogging.java diff --git a/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/EnvironmentPreparedEventForPropertiesLogging.java b/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/EnvironmentPreparedEventForPropertiesLogging.java deleted file mode 100644 index 3dd086b..0000000 --- a/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/EnvironmentPreparedEventForPropertiesLogging.java +++ /dev/null @@ -1,194 +0,0 @@ -package io.github.fbibonne.springaddons.boot.propertieslogger; - -import org.jspecify.annotations.Nullable; -import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; -import org.springframework.context.ApplicationListener; -import org.springframework.core.env.*; -import org.springframework.util.ReflectionUtils; - -import java.lang.reflect.Method; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; - -/** - * Spring ApplicationListener which triggers on {@link ApplicationEnvironmentPreparedEvent} to start properties logging process. - * If the logging is enabled (with property {@code properties.logger.disabled} at {@code false} (which is default value) ) and the - * environment associated with the applicationEnvironmentPreparedEvent is an {@link ConfigurableEnvironment}, then the listener - * will log properties using provided configuration. The responsibility of this class is only to trigger the process and collect the - * configuration for logging properties (properties starting with {@code properties.logger}) in the environment. It delegates the - * logging process to an instance of {@link PropertiesLogger} - */ -public record EnvironmentPreparedEventForPropertiesLogging() implements ApplicationListener { - - private static final LocalLogger log = new LocalLogger(EnvironmentPreparedEventForPropertiesLogging.class); - - private static final Set DEFAULT_PROPS_WITH_HIDDEN_VALUES = Set.of("password", "pwd", "token", "secret", "credential", "pw"); - private static final Set DEFAULT_PREFIX_FOR_PROPERTIES = Set.of("debug", "trace", "info", "logging", "spring", "server", "management", "springdoc", "properties"); - public static final Set DEFAULT_SOURCES_IGNORED = Set.of(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME); - private static final boolean DEFAULT_PROPERTIES_LOGGER_DISABLED = false; - - private static final String KEY_FOR_PROPS_WITH_HIDDEN_VALUES = "properties.logger.with-hidden-values"; - private static final String KEY_FOR_PREFIX_FOR_PROPERTIES = "properties.logger.prefix-for-properties"; - public static final String KEY_FOR_SOURCES_IGNORED = "properties.logger.sources-ignored"; - public static final String KEY_FOR_DISABLED = "properties.logger.disabled"; - - @Override - public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { - final Environment environment = event.getEnvironment(); - if (loggingDisabled(environment)) { - log.debug(() -> "PropertiesLogger is disabled"); - return; - } - abstractEnvironment(environment).ifPresent(this::doLogProperties); - } - - private Optional abstractEnvironment(Environment environment) { - if (environment instanceof ConfigurableEnvironment configurableEnvironment) { - return Optional.of(new CustomAbstractEnvironment(configurableEnvironment)); - } - log.info(()->"Environment "+environment+" is not instance of ConfigurableEnvironment : PropertiesLogger WILL NOT LOG PROPERTIES"); - return Optional.empty(); - } - - private boolean loggingDisabled(Environment environment) { - return getPropertyOrDefaultAndTrace(environment, KEY_FOR_DISABLED, boolean.class, DEFAULT_PROPERTIES_LOGGER_DISABLED); - } - - private void doLogProperties(CustomAbstractEnvironment abstractEnvironment) { - log.debug(() -> "Starting PropertiesLogger on ApplicationEnvironmentPreparedEvent"); - log.trace(() -> "Collecting properties to configure PropertiesLogger"); - final PropertiesWithHiddenValues propertiesWithHiddenValues = new PropertiesWithHiddenValues(getPropertyOrDefaultAndTrace(abstractEnvironment, KEY_FOR_PROPS_WITH_HIDDEN_VALUES, Set.class, DEFAULT_PROPS_WITH_HIDDEN_VALUES)); - final AllowedPrefixForProperties allowedPrefixForProperties = new AllowedPrefixForProperties(getPropertyOrDefaultAndTrace(abstractEnvironment, KEY_FOR_PREFIX_FOR_PROPERTIES, Set.class, DEFAULT_PREFIX_FOR_PROPERTIES)); - final IgnoredPropertySources ignoredPropertySources = new IgnoredPropertySources(getPropertyOrDefaultAndTrace(abstractEnvironment, KEY_FOR_SOURCES_IGNORED, Set.class, DEFAULT_SOURCES_IGNORED)); - - PropertiesLogger propertiesLogger = new PropertiesLogger(propertiesWithHiddenValues, allowedPrefixForProperties, ignoredPropertySources, abstractEnvironment); - propertiesLogger.doLogProperties(); - } - - private T getPropertyOrDefaultAndTrace(PropertyResolver environment, String key, Class clazz, T defaultValue) { - T result; - try { - result = getPropertyOrDefaultAndTraceThrowingException(environment, key, clazz, defaultValue); - } catch (RuntimeException e) { - logExceptionWhenGettingProperty(key, e); - result= defaultValue; - } - return result; - } - - private static void logExceptionWhenGettingProperty(String key, RuntimeException e) { - log.info(()-> "Error while getting property " + key + " : " + e.getMessage()+System.lineSeparator()+"Will use default value"); - } - - private static T getPropertyOrDefaultAndTraceThrowingException(PropertyResolver environment, String key, Class clazz, T defaultValue) throws RuntimeException{ - T result = environment.getProperty(key, clazz, defaultValue); - log.trace(() -> key + " -> " + result); - return result; - } - - static final class CustomAbstractEnvironment implements PropertyResolver { - private static final Method getPropertyResolver = resolveMethod(AbstractEnvironment.class, "getPropertyResolver"); - private static final Method getPropertyAsRawString = resolveMethod(AbstractPropertyResolver.class, "getPropertyAsRawString", String.class); - - private final ConfigurableEnvironment delegate; - @Nullable - private AbstractPropertyResolver propertyResolver; - - - private static Method resolveMethod(Class targetType, String methodName, Class... parameterTypes) { - Method method = Objects.requireNonNull(ReflectionUtils.findMethod(targetType, methodName, parameterTypes)); - ReflectionUtils.makeAccessible(method); - return method; - } - - CustomAbstractEnvironment(ConfigurableEnvironment delegate) { - this.delegate = delegate; - } - - @Nullable - private AbstractPropertyResolver invokeGetPropertyResolver(ConfigurableEnvironment delegate) { - if (delegate instanceof AbstractEnvironment abstractEnvironment) { - var abstractPropertyResolverCondidate = ReflectionUtils.invokeMethod(getPropertyResolver, abstractEnvironment); - if (abstractPropertyResolverCondidate instanceof AbstractPropertyResolver abstractPropertyResolver) { - return abstractPropertyResolver; - } - } - return null; - } - - @Override - public boolean containsProperty(String key) { - return delegate.containsProperty(key); - } - - @Override - public @Nullable String getProperty(String key) { - return delegate.getProperty(key); - } - - @Override - public String getProperty(String key, String defaultValue) { - return delegate.getProperty(key, defaultValue); - } - - @Override - public @Nullable T getProperty(String key, Class targetType) { - return delegate.getProperty(key, targetType); - } - - @Override - public T getProperty(String key, Class targetType, T defaultValue) { - return delegate.getProperty(key, targetType, defaultValue); - } - - @Override - public String getRequiredProperty(String key) throws IllegalStateException { - return delegate.getRequiredProperty(key); - } - - @Override - public T getRequiredProperty(String key, Class targetType) throws IllegalStateException { - return delegate.getRequiredProperty(key, targetType); - } - - @Override - public String resolvePlaceholders(String text) { - return delegate.resolvePlaceholders(text); - } - - @Override - public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException { - return delegate.resolveRequiredPlaceholders(text); - } - - public MutablePropertySources getPropertySources() { - return delegate.getPropertySources(); - } - - @Nullable - public String getPropertySafely(String key) { - try{ - return delegate.getProperty(key); - }catch (IllegalArgumentException e) { - // IllegalArgumentException thrown when unresolved placeholder occurs - return getPropertyAsRawString(key); - } catch (Exception e) { - return "Error while getting property " + key + " : " + e.getMessage(); - } - } - - @Nullable - private String getPropertyAsRawString(String key) { - return invokeGetPropertyAsRawString(key); - } - - @Nullable - private String invokeGetPropertyAsRawString(String key) { - if (this.propertyResolver == null) { - this.propertyResolver = invokeGetPropertyResolver(this.delegate); - } - return (String) ReflectionUtils.invokeMethod(getPropertyAsRawString, this.propertyResolver, key); - } - } -} diff --git a/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertiesLogger.java b/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertiesLogger.java index 34b5386..e3965a7 100644 --- a/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertiesLogger.java +++ b/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertiesLogger.java @@ -1,6 +1,7 @@ package io.github.fbibonne.springaddons.boot.propertieslogger; import org.jspecify.annotations.Nullable; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.PropertySource; @@ -29,16 +30,16 @@ public class PropertiesLogger { final PropertiesWithHiddenValues propertiesWithHiddenValues; final AllowedPrefixForProperties allowedPrefixForProperties; final IgnoredPropertySources ignoredPropertySources; - final EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment abstractEnvironment; + final ConfigurableEnvironment configurableEnvironment; final OriginFinder originFinder; - PropertiesLogger(PropertiesWithHiddenValues propertiesWithHiddenValues, AllowedPrefixForProperties allowedPrefixForProperties, IgnoredPropertySources ignoredPropertySources, EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment abstractEnvironment) { + PropertiesLogger(PropertiesWithHiddenValues propertiesWithHiddenValues, AllowedPrefixForProperties allowedPrefixForProperties, IgnoredPropertySources ignoredPropertySources, ConfigurableEnvironment configurableEnvironment) { this.propertiesWithHiddenValues = propertiesWithHiddenValues; this.allowedPrefixForProperties = allowedPrefixForProperties; this.ignoredPropertySources = ignoredPropertySources; - this.abstractEnvironment = abstractEnvironment; - this.originFinder = new OriginFinder(abstractEnvironment.getPropertySources()); + this.configurableEnvironment = configurableEnvironment; + this.originFinder = new OriginFinder(this.configurableEnvironment.getPropertySources()); } /** @@ -58,7 +59,7 @@ public class PropertiesLogger { *
  • for each propertySource not excluded, list all property keys then exclude {@code null} keys and non-allowed prefixed ones (see property {@code properties.logger.prefix-for-properties}
  • *
  • order distinct keys with alphabetical order (natural order of {@link String}
  • *
  • for each key, compute an expression {@code key = value ### FROM value_origin ###} where {@code value} is the value of the key resolved against - * the environment ({@link PropertiesLogger#abstractEnvironment}) or masked if key is listed in property {@code properties.logger.with-hidden-values}.
  • + * the environment ({@link PropertiesLogger#configurableEnvironment}) or masked if key is listed in property {@code properties.logger.with-hidden-values}. *
  • log the list of used propertySources to find keys, the ordered list of properties and their values and origin when available
  • * * @@ -104,7 +105,7 @@ private static Stream distinctPropertiesNames(Map prop private Map propertyNamesBySourceFromEnvironment() { Map propertyNamesBySource = new HashMap<>(); - for (PropertySource propertySource : this.abstractEnvironment.getPropertySources()) { + for (PropertySource propertySource : this.configurableEnvironment.getPropertySources()) { asEnumerablePropertySourceIfHasToBeProcessed(propertySource) .ifPresent(enumerablePropertySource -> propertyNamesBySource.put(enumerablePropertySource.getName(), enumerablePropertySource.getPropertyNames()) @@ -147,7 +148,7 @@ private static void traceIgnored(PropertySource propertySource) { private String toDisplayedLine(String key) { return ANSI_CYAN_BOLD_SEQUENCE + key + ANSI_NORMAL_SEQUENCE + " = " - + ANSI_BROWN_UNDERLINE_SEQUENCE + resolveValueThenMaskItIfSecret(key, this.abstractEnvironment) + ANSI_NORMAL_SEQUENCE + + ANSI_BROWN_UNDERLINE_SEQUENCE + resolveValueThenMaskItIfSecret(key) + ANSI_NORMAL_SEQUENCE + this.originFinder.findOriginFor(key).map(PropertiesLogger::originAsLine).orElse(""); } @@ -155,11 +156,11 @@ private static String originAsLine(String origin) { return " ### " + AINSI_PURPLE_ITALIC_SEQUENCE + origin + ANSI_NORMAL_SEQUENCE + " ###"; } - private @Nullable String resolveValueThenMaskItIfSecret(String key, EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment environment) { + private @Nullable String resolveValueThenMaskItIfSecret(String key) { if (mustBeMasked(key)) { return MASK; } - return environment.getPropertySafely(key); + return this.configurableEnvironment.getProperty(key); } private boolean mustBeMasked(String key) { diff --git a/src/test/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertiesLoggerTest.java b/src/test/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertiesLoggerTest.java index 853ecfe..f6083b2 100644 --- a/src/test/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertiesLoggerTest.java +++ b/src/test/java/io/github/fbibonne/springaddons/boot/propertieslogger/PropertiesLoggerTest.java @@ -34,7 +34,7 @@ class PropertiesLoggerTest { }) void isValueContainedIgnoringCaseInTest(String container, String value, boolean expected) { PropertiesLogger propertiesLogger = new PropertiesLogger(null, null, null, - new EnvironmentPreparedEventForPropertiesLogging.CustomAbstractEnvironment(new MockEnvironment()) + new MockEnvironment() ); assertThat(propertiesLogger.isValueContainedIgnoringCaseIn(container).test(value)).isEqualTo(expected); } diff --git a/src/test/java/io/github/fbibonne/test/ExternalPropertiesFilesIntegrationTest.java b/src/test/java/io/github/fbibonne/test/ExternalPropertiesFilesIntegrationTest.java index 81e6c3e..96c32a7 100644 --- a/src/test/java/io/github/fbibonne/test/ExternalPropertiesFilesIntegrationTest.java +++ b/src/test/java/io/github/fbibonne/test/ExternalPropertiesFilesIntegrationTest.java @@ -9,6 +9,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.core.io.ClassPathResource; +import org.springframework.util.PlaceholderResolutionException; import java.io.IOException; import java.nio.file.Path; @@ -41,7 +42,7 @@ void startingSpringBootApplicationWithExternalOptionalPropertiesFilesAndUnexisti "--properties.logger.prefix-for-properties=info,logging,spring,server,management,properties,springdoc,io", "--spring.main.web-application-type=none")).doesNotThrowAnyException(); Environment environment = contextRef.context.getEnvironment(); - assertThatCode(()->environment.getProperty("spring.config.location")).isInstanceOf(IllegalArgumentException.class).hasMessageContaining("Could not resolve placeholder 'unexisting'"); + assertThatCode(()->environment.getProperty("spring.config.location")).isInstanceOf(PlaceholderResolutionException.class).hasMessageContaining("Could not resolve placeholder 'unexisting'"); assertThat(capturedOutput.toString()).contains("spring.config.location"+ANSI_NORMAL_SEQUENCE+" = "+ANSI_BROWN_UNDERLINE_SEQUENCE+"optional:file:/${unexisting}/application.properties"); //otherProps/application.properties /* diff --git a/src/test/java/io/github/fbibonne/test/examples/WithEnvironmentVariableTest.java b/src/test/java/io/github/fbibonne/test/examples/WithEnvironmentVariableTest.java index f76e449..cd08f72 100644 --- a/src/test/java/io/github/fbibonne/test/examples/WithEnvironmentVariableTest.java +++ b/src/test/java/io/github/fbibonne/test/examples/WithEnvironmentVariableTest.java @@ -18,7 +18,7 @@ class WithEnvironmentVariableTest { @Test void shouldPickKeyFromApplicationPropertiesAndValueFromEnvironmentVariable(CapturedOutput output) { String logOutput = output.toString(); - + assertThat(System.getenv("SPRING_DATASOURCE_USERNAME")).hasToString("user_prod"); assertThat(logOutput).contains("spring.datasource.username"+ANSI_NORMAL_SEQUENCE+" = "+ANSI_BROWN_UNDERLINE_SEQUENCE+"user_prod"+ANSI_NORMAL_SEQUENCE+" ### "+AINSI_PURPLE_ITALIC_SEQUENCE+"FROM System Environment Property \"SPRING_DATASOURCE_USERNAME\""+ANSI_NORMAL_SEQUENCE+" ###"); } From 32669df0583effefa9125a065a343e5a83b2f7cf Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Tue, 10 Mar 2026 18:06:22 +0100 Subject: [PATCH 2/2] refactor: Use spring boot api instead of custom extensions to manage unresolved placeholders in properties : OK - Remove custom class extension with acces to non-public spring boot classes --- ...mentPreparedEventForPropertiesLogging.java | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/EnvironmentPreparedEventForPropertiesLogging.java diff --git a/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/EnvironmentPreparedEventForPropertiesLogging.java b/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/EnvironmentPreparedEventForPropertiesLogging.java new file mode 100644 index 0000000..e2ca1d0 --- /dev/null +++ b/src/main/java/io/github/fbibonne/springaddons/boot/propertieslogger/EnvironmentPreparedEventForPropertiesLogging.java @@ -0,0 +1,91 @@ +package io.github.fbibonne.springaddons.boot.propertieslogger; + +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; +import org.springframework.core.env.PropertyResolver; +import org.springframework.core.env.StandardEnvironment; + +import java.util.Optional; +import java.util.Set; + +/** + * Spring ApplicationListener which triggers on {@link ApplicationEnvironmentPreparedEvent} to start properties logging process. + * If the logging is enabled (with property {@code properties.logger.disabled} at {@code false} (which is default value) ) and the + * environment associated with the applicationEnvironmentPreparedEvent is an {@link ConfigurableEnvironment}, then the listener + * will log properties using provided configuration. The responsibility of this class is only to trigger the process and collect the + * configuration for logging properties (properties starting with {@code properties.logger}) in the environment. It delegates the + * logging process to an instance of {@link PropertiesLogger} + */ +public record EnvironmentPreparedEventForPropertiesLogging() implements ApplicationListener { + + private static final LocalLogger log = new LocalLogger(EnvironmentPreparedEventForPropertiesLogging.class); + + private static final Set DEFAULT_PROPS_WITH_HIDDEN_VALUES = Set.of("password", "pwd", "token", "secret", "credential", "pw"); + private static final Set DEFAULT_PREFIX_FOR_PROPERTIES = Set.of("debug", "trace", "info", "logging", "spring", "server", "management", "springdoc", "properties"); + public static final Set DEFAULT_SOURCES_IGNORED = Set.of(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME); + private static final boolean DEFAULT_PROPERTIES_LOGGER_DISABLED = false; + + private static final String KEY_FOR_PROPS_WITH_HIDDEN_VALUES = "properties.logger.with-hidden-values"; + private static final String KEY_FOR_PREFIX_FOR_PROPERTIES = "properties.logger.prefix-for-properties"; + public static final String KEY_FOR_SOURCES_IGNORED = "properties.logger.sources-ignored"; + public static final String KEY_FOR_DISABLED = "properties.logger.disabled"; + + @Override + public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { + final Environment environment = event.getEnvironment(); + if (loggingDisabled(environment)) { + log.debug(() -> "PropertiesLogger is disabled"); + return; + } + configurableEnvironment(environment).ifPresent(this::doLogProperties); + } + + private Optional configurableEnvironment(Environment environment) { + if (environment instanceof ConfigurableEnvironment configurableEnvironment) { + return Optional.of(configurableEnvironment); + } + log.info(()->"Environment "+environment+" is not instance of ConfigurableEnvironment : PropertiesLogger WILL NOT LOG PROPERTIES"); + return Optional.empty(); + } + + private boolean loggingDisabled(Environment environment) { + return getPropertyOrDefaultAndTrace(environment, KEY_FOR_DISABLED, boolean.class, DEFAULT_PROPERTIES_LOGGER_DISABLED); + } + + private void doLogProperties(ConfigurableEnvironment configurableEnvironment) { + log.debug(() -> "Starting PropertiesLogger on ApplicationEnvironmentPreparedEvent"); + log.trace(() -> "Collecting properties to configure PropertiesLogger"); + + configurableEnvironment.setIgnoreUnresolvableNestedPlaceholders(true); + final PropertiesWithHiddenValues propertiesWithHiddenValues = new PropertiesWithHiddenValues(getPropertyOrDefaultAndTrace(configurableEnvironment, KEY_FOR_PROPS_WITH_HIDDEN_VALUES, Set.class, DEFAULT_PROPS_WITH_HIDDEN_VALUES)); + final AllowedPrefixForProperties allowedPrefixForProperties = new AllowedPrefixForProperties(getPropertyOrDefaultAndTrace(configurableEnvironment, KEY_FOR_PREFIX_FOR_PROPERTIES, Set.class, DEFAULT_PREFIX_FOR_PROPERTIES)); + final IgnoredPropertySources ignoredPropertySources = new IgnoredPropertySources(getPropertyOrDefaultAndTrace(configurableEnvironment, KEY_FOR_SOURCES_IGNORED, Set.class, DEFAULT_SOURCES_IGNORED)); + + PropertiesLogger propertiesLogger = new PropertiesLogger(propertiesWithHiddenValues, allowedPrefixForProperties, ignoredPropertySources, configurableEnvironment); + propertiesLogger.doLogProperties(); + } + + private T getPropertyOrDefaultAndTrace(PropertyResolver environment, String key, Class clazz, T defaultValue) { + T result; + try { + result = getPropertyOrDefaultAndTraceThrowingException(environment, key, clazz, defaultValue); + } catch (RuntimeException e) { + logExceptionWhenGettingProperty(key, e); + result= defaultValue; + } + return result; + } + + private static void logExceptionWhenGettingProperty(String key, RuntimeException e) { + log.info(()-> "Error while getting property " + key + " : " + e.getMessage()+System.lineSeparator()+"Will use default value"); + } + + private static T getPropertyOrDefaultAndTraceThrowingException(PropertyResolver environment, String key, Class clazz, T defaultValue) throws RuntimeException{ + T result = environment.getProperty(key, clazz, defaultValue); + log.trace(() -> key + " -> " + result); + return result; + } + +}