Search

Dark theme | Light theme
Showing posts with label SpringSweets:Configuration. Show all posts
Showing posts with label SpringSweets:Configuration. Show all posts

February 8, 2025

Spring Sweets: Use Logging in EnvironmentPostProcessor Implementation

You can write an implementation of the interface EnvironmentPostProcessor to customize the Environment of a Spring Boot application. For example you can read an external configuration file and add its properties to the Environment. If you want to add some logging statement to the class then you need to make sure you pass a DeferredLogFactory to the constructor of the class. From this factory you can use the getLogger method to get a Log instance. This is needed, because the implementation of the interface EnvironmentPostProcessor is created before the logging system is initialized. By using the Log instances created from DeferredLogFactory Spring Boot will make sure the log messages are written when the logging system is initialized.

October 10, 2024

Spring Sweets: Using Duration Type With Configuration Properties

With @ConfigurationProperties in Spring Boot we can bind configuration properties to Java classes. The class annotated with @ConfigurationProperties can be injected into other classes and used in our code. We can use the type Duration to configure properties that express a duration. When we set the value of the property we can use:

  • a long value with the unit to express milliseconds,
  • a value following the ISO-8601 duration format,
  • a special format supported by Spring Boot with the value and unit.

We can also use the @DurationUnit annotation to specify the unit for a long value. So instead of the default milliseconds we can specify the unit to be seconds or minutes for example. The unit is defined by the java.time.temporal.ChronoUnit enum and we pass it as an argument to the annotation.

The special format supported by Spring Boot supports the following units:

  • ns for nanoseconds,
  • us for microseconds,
  • ms for milliseconds,
  • s for seconds,
  • m for minutes,
  • h for hours,
  • d for days.

In the following example we define a record TimeoutProperties annotated with @ConfigurationProperties and four properties of type Duration. The property idleTimeout has the @DurationUnit annotation to specify the unit to be seconds.

package mrhaki;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;

import java.time.Duration;
import java.time.temporal.ChronoUnit;

@ConfigurationProperties(prefix = "timeout")
public record TimeoutProperties(
    Duration connectionTimeout,
    Duration readTimeout,
    Duration writeTimeout,
    @DurationUnit(ChronoUnit.SECONDS) Duration idleTimeout
) {}

In our application.properties file we can set the values of the properties:

# long value (in milliseconds)
timeout.connection-timeout=5000

# ISO-8601 format
timeout.read-timeout=PT30S

# Spring Boot's format
timeout.write-timeout=1m

# value in seconds (due to @DurationUnit annotation)
timeout.idle-timeout=300

In the following test we test the values of the properties in the class TimeoutProperties.

package mrhaki;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;

import java.time.Duration;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest
@TestPropertySource(properties = {
    "timeout.connection-timeout=5000", // use long value in milliseconds
    "timeout.read-timeout=PT30S", // use ISO-8601 duration format
    "timeout.write-timeout=1m", // use special format supported by Spring Boot
    "timeout.idle-timeout=300" // use long value in seconds (set by @DurationUnit)
})
class TimeoutPropertiesTest {

    @Autowired
    private TimeoutProperties timeoutProperties;

    @Test
    void testConnectionTimeout() {
        assertThat(timeoutProperties.connectionTimeout())
            .isEqualTo(Duration.ofMillis(5000))
            .isEqualTo(Duration.ofSeconds(5));
    }

    @Test
    void testReadTimeout() {
        assertThat(timeoutProperties.readTimeout())
            .isEqualTo(Duration.ofSeconds(30));
    }

    @Test
    void testWriteTimeout() {
        assertThat(timeoutProperties.writeTimeout())
            .isEqualTo(Duration.ofMinutes(1))
            .isEqualTo(Duration.ofSeconds(60));
    }


    @Test
    void testIdleTimeout() {
        assertThat(timeoutProperties.idleTimeout())
            .isEqualTo(Duration.ofSeconds(300))
            .isEqualTo(Duration.ofMinutes(5));
    }

    @Test
    void testTimeoutToMillis() {
        assertThat(timeoutProperties.connectionTimeout().toMillis()).isEqualTo(5000);
        assertThat(timeoutProperties.readTimeout().toMillis()).isEqualTo(30000);
        assertThat(timeoutProperties.writeTimeout().toMillis()).isEqualTo(60000);
        assertThat(timeoutProperties.idleTimeout().toMillis()).isEqualTo(300000);
    }
}

Written with Spring Boot 3.4.4.

March 17, 2017

Spring Sweets: Access Application Arguments With ApplicationArguments Bean

When we start a Spring Boot application and pass arguments to the application, Spring Boot will capture those arguments and creates a Spring bean of type ApplicationArguments and puts it in the application context. We can use this Spring bean to access the arguments passed to the application. We could for example auto wire the bean in another bean and use the provided argument values. The ApplicationArguments interface has methods to get arguments values that are options and plain argument values. An option argument is prefixed with --, for example --format=xml is a valid option argument. If the argument value is not prefixed with -- it is a plain argument.

In the following example application we have a Spring Boot application with a Spring bean that implements CommandLineRunner. When we define the CommandLineRunner implementation we use the ApplicationArguments bean that is filled by Spring Boot:

package mrhaki.springboot;

import org.jooq.DSLContext;
import org.jooq.Record1;
import org.jooq.Result;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.List;

import static mrhaki.springboot.sql.Tables.USERS;

@SpringBootApplication
public class SampleApp {

    /**
     * Run the application, let Spring Boot
     * decide the exit code and use the
     * exit code value for {@code System.exit()} method.
     */
    public static void main(String[] args) {
        SpringApplication.run(SampleApp.class, args);
    }

    /**
     * Find users based on user input given via application arguments.
     * Example usage: {@code java -jar sample.jar --format=csv mrhaki}
     * 
     * @param dslContext JOOQ context to access data.
     * @param applicationArguments Bean filled by Spring Boot 
     *                             with arguments used to start the application
     * @return Bean to run at startup
     */
    @Bean
    CommandLineRunner showUsers(
            final DSLContext dslContext, 
            final ApplicationArguments applicationArguments) {
        
        return (String... arguments) -> {
            // Get arguments passed to the application  that are
            // not option arguments from ApplicationArguments bean.
            // In the following example: java -jar sample.jar mrhaki
            // mrhaki is the non option argument.
            final String name = applicationArguments.getNonOptionArgs().get(0);
            final Result<Record1<String>> result =
                    dslContext.select(USERS.NAME)
                              .from(USERS)
                              .where(USERS.NAME.likeIgnoreCase(name))
                              .fetch();

            // Check if option argument --format is used.
            // In the following example: java -jar sample.jar --format=xml --format=csv
            // format is the option argument with two values: xml, csv.
            if (applicationArguments.containsOption("format")) {
                // Get values for format option argument.
                final List<String> format = applicationArguments.getOptionValues("format");
                if (format.contains("xml")) {
                    result.formatXML(System.out);
                }
                if (format.contains("json")) {
                    result.formatJSON(System.out);
                }
                if (format.contains("html")) {
                    result.formatHTML(System.out);
                }
                if (format.contains("html")) {
                    result.formatCSV(System.out);
                }
            } else {
                result.format(System.out);
            }
        };
    }

}

We have an alternative if we want to use the ApplicationArguments bean in a CommandlineRunner implementation. Spring Boot offers the ApplicationRunner interface. The interface has the method run(ApplicationArguments) we need to implement and gives us directly access to the ApplicationArguments bean. In the next example we refactor the application and use ApplicationRunner:

package mrhaki.springboot;

import org.jooq.DSLContext;
import org.jooq.Record1;
import org.jooq.Result;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.List;

import static mrhaki.springboot.sql.Tables.USEaRS;

@SpringBootApplication
public class SampleApp {

    /**
     * Run the application, let Spring Boot
     * decide the exit code and use the
     * exit code value for {@code System.exit()} method.
     */
    public static void main(String[] args) {
        SpringApplication.run(SampleApp.class, args);
    }

    /**
     * Find users based on user input given via application arguments.
     * Example usage: {@code java -jar sample.jar --format=csv mrhaki}
     *
     * @param dslContext JOOQ context to access data.
     * @return Bean to run at startup
     */
    @Bean
    ApplicationRunner showUsers(final DSLContext dslContext) {
        return (ApplicationArguments arguments) -> {
            // Get arguments passed to the application that are
            // not option arguments. 
            // In the following example: java -jar sample.jar mrhaki
            // mrhaki is the non option argument.
            final String name = arguments.getNonOptionArgs().get(0);
            final Result<Record1<String>> result =
                    dslContext.select(USERS.NAME)
                              .from(USERS)
                              .where(USERS.NAME.likeIgnoreCase(name))
                              .fetch();

            // Check if option argument --format is used.
            // In the following example: java -jar sample.jar --format=xml --format=csv
            // format is the option argument with two values: xml, csv.
            if (arguments.containsOption("format")) {
                // Get values for format option argument.
                final List<String> format = arguments.getOptionValues("format");
                if (format.contains("xml")) {
                    result.formatXML(System.out);
                } 
                if (format.contains("json")) {
                    result.formatJSON(System.out);
                }
                if (format.contains("html")) {
                    result.formatHTML(System.out);
                }
                if (format.contains("html")) {
                    result.formatCSV(System.out);
                }
            } else {
                result.format(System.out);
            }
        };
    }

}

Written with Spring Boot 1.5.2.RELEASE.

July 2, 2016

Spring Sweets: Using Groovy Configuration As PropertySource

We have many ways to provide configuration properties to a Spring (Boot) application. We can add our own custom configuration properties format. For example we can use Groovy's ConfigObject object to set configuration properties. We need to read a configuration file using ConfigSlurper and make it available as a property source for Spring. We need to implement two classes and add configuration file to support a Groovy configuration file in a Spring application.

First we need to write a class that extends the PropertySource in the package org.springframework.core.env. This class has methods to get property values based on a given key. There are already some subclasses for specific property sources. There is for example also a MapPropertySource class. We will extend that class for our implementation, because we can pass our flattened ConfigObject and rely on all existing functionality of the MapPropertySource class:

// File: src/main/java/mrhaki/spring/configobject/ConfigObjectPropertySource.java
package mrhaki.spring.configobject;

import groovy.util.ConfigObject;
import org.springframework.core.env.MapPropertySource;

/**
 * Property source that supports {@link ConfigObject}. The {@link ConfigObject}
 * is flattened and all functionality is delegated to the
 * {@link MapPropertySource}.
 */
public class ConfigObjectPropertySource extends MapPropertySource {
    
    public ConfigObjectPropertySource(
            final String name, 
            final ConfigObject config) {
        
        super(name, config.flatten());
    }
    
}

Next we must implement a class that loads the configuration file. The configuration file must be parsed and we must our ConfigObjectPropertySource so we can use it in our Spring context. We need to implement the PropertySourceLoader interface, where we can define the file extensions the loader must be applied for. Also we override the load method to load the actual file. In our example we also add some extra binding variables we can use when we define our Groovy configuration file.

// File: src/main/java/mrhaki/spring/configobject/ConfigObjectPropertySourceLoader.java
package mrhaki.spring.configobject;

import groovy.util.ConfigObject;
import groovy.util.ConfigSlurper;
import org.springframework.boot.env.PropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.util.ClassUtils;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class ConfigObjectPropertySourceLoader implements PropertySourceLoader {

    /**
     * Allowed Groovy file extensions for configuration files.
     *
     * @return List of extensions for Groovy configuration files.
     */
    @Override
    public String[] getFileExtensions() {
        return new String[]{"groovy", "gvy", "gy", "gsh"};
    }

    /**
     * Load Groovy configuration file with {@link ConfigSlurper}.
     */
    @Override
    public PropertySource<?> load(
            final String name,
            final Resource resource,
            final String profile) throws IOException {

        if (isConfigSlurperAvailable()) {
            final ConfigSlurper configSlurper = profile != null ?
                    new ConfigSlurper(profile) :
                    new ConfigSlurper();

            // Add some extra information that is accessible
            // in the Groovy configuration file.
            configSlurper.setBinding(createBinding(profile));
            final ConfigObject config = configSlurper.parse(resource.getURL());

            // Return ConfigObjectPropertySource if configuration
            // has key/value pairs.
            return config.isEmpty() ?
                    null :
                    new ConfigObjectPropertySource(name, config);
        }

        return null;
    }

    private boolean isConfigSlurperAvailable() {
        return ClassUtils.isPresent("groovy.util.ConfigSlurper", null);
    }

    private Map<String, Object> createBinding(final String profile) {
        final Map<String, Object> bindings = new HashMap<>();
        bindings.put("userHome", System.getProperty("user.home"));
        bindings.put("appDir", System.getProperty("user.dir"));
        bindings.put("springProfile", profile);
        return bindings;
    }

}

Finally we need to add our ConfigObjectPropertySourceLoader to a Spring configuration file, so it used in our Spring application. We need to create the file spring.factories in the META-INF directory with the following contents:

# File: src/main/resources/META-INF/spring.factories
org.springframework.boot.env.PropertySourceLoader=\
  mrhaki.spring.configobject.ConfigObjectPropertySourceLoader

Now when we create a Spring application with all our classes and configuration file in the classpath we can provide an application.groovy file in the root of the classpath:

// File: src/main/resources/application.groovy
app {
    // Using binding property appDir,
    // which is added to the ConfigSlurper 
    // in ConfigObjectPropertySourceLoader.
    startDir = appDir

    // Configuration hierarchy.
    message {
        text = "Text from Groovy configuration!"
    }
}

environments {
    // When starting application with 
    // -Dspring.profiles.active=blog this
    // environment configuration is used.
    blog {
        app {
            message {
                text = "Groovy Goodness"
            }
        }
    }
}

Written with Spring Boot 1.3.5.RELEASE